Provisioning Sample in C#

by Walter Oliver

Overview

Over the years, it has become simple for advanced Web users to create their own Web site. Once an end-user has registered their domain name, there are various types of hosters to choose from. Web hosters are available throughout the world to meet the demands of end customers. But regardless of which hoster is chosen, the scenario of signing up and creating a web site is similar in all cases.

Initially, a new user account needs to be established for the user. Once the account has been set up, the end user decides what features and options the site should incorporate: for instance, how much disk space, FTP capability, creation of virtual directories, whether or not databases are needed, etc. Hosters build control panels or dashboard applications that allow end users to create and manage these features.

There are a number of ways that these capabilities can be implemented into the control panel. In this section, we look at implementing the provisioning aspect of these features through managed code. An outline of the features is as follows:

Provision a New User Account

The site owner who manages and maintains the site needs a new user account. The account can either be an Active Directory® account or a local user account. The following code fragments demonstrate the creation of a local account. Note that the System.DirectoryServices namespace must be specified.

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

Create Content Storage

A Web site requires a location on the file systems to which users can upload content for the site. The Microsoft® .NET Directory Class provides the application programming interface (API) to create directories on the file system.

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

The content storage requires specific permissions configured so that users can manage their own contents. The following code fragment demonstrates how to set directory permissions using managed code in 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;
}

The following code fragment demonstrates how to set disk quota using managed code if the disk quota is restricted. To use the disk quota management, you must add reference to the Windows® disk quota management component; it resides under 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;
}

Create an Application Pool

An application pool defines the settings for a worker process that hosts one or more Internet Information Services 7 (IIS 7) applications, which carry out their request processing. The application pool is a unit of process isolation, since all request processing for an application runs within its application pool's worker processes.

An application pool also provides isolation, which in turn provides security. Each application pool can run with a unique identity and can use an access control list (ACL) to prevent applications in other pools from accessing its resources. The following code fragment demonstrates the creation of an application pool, setting the identity, and setting properties.

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

Create a Site

A site is the top-level logical container that specifies how HTTP requests are received and processed. A site defines a group of bindings that determine how the site listens for incoming requests, and a site contains the definitions of applications or virtual directories that partition the site's URL namespace for the purposes of structuring the application content.

The following code fragment demonstrates how to create a new site. The Microsoft.Web.Administration namespaces are necessary when implementing the ServerManager object. Note that the application collection, site collection, and binding objects are all accessed through the ServerManager object.

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

Create a Binding

A bindingis a combination of the protocol name and protocol-specific binding information. While IIS 7 supports multi-protocol bindings (such as the Windows® Communication Foundation [WCF] SOAP-TCP and FTP), the code that follows uses the HTTP path only; an HTTP binding effectively defines an HTTP endpoint that listens on a specific interface IP address (or all interfaces), a specific port number, or a specific HTTP host header (or all host headers). In this way, you can configure many sites on your server that listen on different IP addresses, different ports, or on the same IP address or port but with different host headers.

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

Create a Root Application

An applicationis a logical container for the Web site functionality, letting you divide the site URL namespace into separate parts and control the runtime behavior of each part individually.

For example, you can configure each application to reside in a separate application pool. This isolates an application from other applications by putting it in a separate process, and optionally making that process run with a different Windows identity to sandbox it.

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

Create a Virtual Directory

A virtual directory maps a part of the application URL namespace to a physical location on disk. When a request is routed to the application, it uses the same algorithm to find the virtual directory with the longest virtual path matching the remainder of the request's absolute path after the application path.

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

Create an FTP Site

An FTP site lets customers upload content to their Web site and provides the ability to move files across the Internet. The customer can manage both content and access. The creation of an FTP site is similar to that of a Web site: An application pool and site is created with a root application and virtual directory, and then an FTP binding is applied.

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