ASP.NET web-based Site Installer
I had the need for this very thing so I thought I would write a small app that I can re-use on my many web-based applications.
Just as a side note... this is not MVC specific. I know this is a Blog for MVC Starter Kits but this project can be used to install any web site.
What is the Web-based Site Installer?
This is an ASP.NET 2.0 AJAX web site application that you can plug into your web application (if it is meant to be distributed and installed by your customers) and have your customer be able to easily install the application through their browser.

There are more screen shots at the end of this blog post.
It's built on a simple ASP.NET Wizard control that gathers information needed to set up the application, usually database information, and then run several methods that do the following:
1) Modify the web.config to add your connection string
2) Create the database or use the one you selected
3) Run scripts that add the database tables (of your choice) to the selected database
4) Add the ASP.NET Membership tables to the database
5) Creates Roles (Administrators, Users)
6) Create an Administrator account based on user input
7) Assigns that administrator account to the Administrators Role
When the process is completed, you can log on as the account you just created and verify that it all works!
This will work with SQL Server and SQL Server Express. MySql is not implemented at this time. Maybe in a future version.
Pre-requesites
1) You must have permission to the SQL Server instance you will be using
2) You must have already created the user for this setup. In a shared web hosting situation, this would normally be handled through a control panel. In the control panel you would setup your database and credentials and apply the necessary permissions.
3) The folder that contains the web.config (or external config file if you use the configSource attribute of the connection strings section), needs to have Read and Write permissions from NETWORK SERVICE. Your control panel should be able to apply these permissions. If they don't, contact your web host for help.
So assuming you have all the pre-requesites in place, let's continue.
Let's see how this works. There are essentially 5 steps to the wizard.
1) Collect Server information - assign the server you will use, i.e.: localhost, localhost\SQLEXPRESS, sql.myserver.com, etc.
2) Choose database - you can choose from a list of available (SQL Server) databases, or enter the name of a new database
3) Enter Admin account info - enter the information for the admin account. This will be entered into the ASP.NET 2.0 Membership tables.
4) Confirm your settings and execute the process.
5) Process completed!
So how does the installer know to go to the /install directory?
There's a small class that probes the web.config file and looks into the connectionStrings section. If it finds this:
<connectionStrings>
<clear />
<add name="appConnection" connectionString="##NOT_IMPLEMENTED##" providerName="System.Data.SqlClient" />
</connectionStrings>
... then it knows that this is the first time the application is being run.
This is handled by the class, SetupUtility. It contains a read-only property that returns a boolean value whether the connection string(s) are implemented. It is called in the Global.asax file.
public class SetupUtility
{
// This string needs to added to the web.config/connectionStrings section on new applications.
// This is what tells the application that it's being used for the first time.
// You can put anything here, as long as it matches what is in the connectionString attribute value
// in the connectionStrings section of web.confg.
private const string NOTIMPLEMENTED = "##NOT_IMPLEMENTED##";
/// <summary>
/// Check all connection strings for ##NOT_IMPLEMENTED##. If it exists
/// in the connections strings section, it is the first time and we need
/// to redirect to the install folder.
/// </summary>
/// <returns></returns>
public static bool isFirstTime
{
get
{
bool firstTime = false;
ConnectionStringSettingsCollection connStrings = WebConfigurationManager.ConnectionStrings;
foreach (ConnectionStringSettings conn in connStrings)
{
if (conn.ConnectionString == NOTIMPLEMENTED)
{
firstTime = true;
break;
}
}
return firstTime;
}
}
}
It also contains a little generic helper method that can find controls within a starting control. I use it in the AttachEventHandlerToFinishButton() method that tries to find the FinishButton in the wizInstall ASP.NET Wizard Control.
/// <summary>
/// You can return a control of a Type based on the ID and the starting Control.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="startingControl"></param>
/// <param name="id"></param>
/// <returns></returns>
public static T FindControl<T>(System.Web.UI.Control startingControl, string id) where T : System.Web.UI.Control
{
T found = null;
foreach (System.Web.UI.Control activeControl in startingControl.Controls)
{
found = activeControl as T;
if (found == null)
{
found = FindControl<T>(activeControl, id);
}
else if (string.Compare(id, found.ID, true) != 0)
{
found = null;
}
if (found != null)
{
break;
}
}
return found;
}
In the Global.asax file, this class is called in the Application_Start() event, and the isFirstTime property returns a Boolean value on whether the web.config connection strings section has been set. If it still has the ##NOT_IMPLEMENTED## place holder, then it has not been set and the application redirects to the install page.
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
if (SetupUtility.isFirstTime)
{
System.Web.HttpContext.Current.Response.Redirect("~/install/install.aspx", false);
}
}
That's what initiates the process. Once the application re-starts, it won't go to the /install directory, or if it reads the connection strings section of the web.config file and all connection strings are set, then it will not go to the /install directory.
Install.aspx - code behind
All of the wizard processing occurs in the install.aspx code-behind. The first few steps of the wizard collects the necessary information and the final button click runs the process.
This is the method that is called in the Finish button click event.
/// <summary>
/// Run all the functions to install the database tables and set the admin account.
/// </summary>
private void RunAll()
{
// If we are using an existing datbase, we don't need to create it.
if (!(bool)ViewState["UseExistingDb"])
{
// Create the Test database
if (!CreateDb()) throw new Exception("Error creating database!");
}
// Add the tables to the database
RunScripts(ViewState["DBName"].ToString());
// Add the ASPNETDB tables to the database using the SqlServices Install method.
// This will add the ASPNETDB tables to the same database as the application.
// NOTE: This method can ONLY be used for SQL Server. To point to MySql,
// you will need to create the database scripts for MySql and add them to
// the RunScripts method.
// Special thanks to the article by Peter Bromberg on the
// System.Web.Management.SqlServices.Install method at http://www.eggheadcafe.com/articles/20060529.asp
if (chkTrustedConnection.Checked)
{
// For SQL Server Trusted connections
System.Web.Management.SqlServices.Install(txtServerName.Text.Trim(), ViewState["DBName"].ToString(), System.Web.Management.SqlFeatures.All);
}
else
{
// For SQL Server
System.Web.Management.SqlServices.Install(txtServerName.Text.Trim(), ViewState["DbUserName"].ToString(), ViewState["DbPassword"].ToString(), ViewState["DBName"].ToString(), System.Web.Management.SqlFeatures.All);
}
// Create the Roles, Administrators, Users
if (!Roles.RoleExists(ADMINISTRATORS_ROLE)) Roles.CreateRole(ADMINISTRATORS_ROLE);
if (!Roles.RoleExists(USERS_ROLE)) Roles.CreateRole(USERS_ROLE);
// Create the Admin User
MembershipCreateStatus status = MembershipCreateStatus.UserRejected;
MembershipUser user = Membership.CreateUser(txtAdminUserName.Text.Trim(), ViewState["AdminPassword"].ToString(), txtAdminEmail.Text.Trim(),
txtSecretQuestion.Text.Trim(), txtSecretAnswer.Text.Trim(), true, out status);
// Assign the Admin user to the Administrators role
if (status == MembershipCreateStatus.Success)
{
Roles.AddUserToRole(txtAdminUserName.Text, ADMINISTRATORS_ROLE);
}
}
Let's go over the steps in the RunAll() method. The first "if" statement checks whether the selected database already exists, if it does, we simply bypass this method. Otherwise, we create the database.
The RunScripts() method takes the name of the database and runs any number of SQL scripts (that you provide) to create tables in your database, import sample data, drop tables, or whatever you need to do.
/// <summary>
/// Run SQL scripts to create the tables and any other sql scripts.
/// </summary>
/// <param name="dbName"></param>
private void RunScripts(string dbName)
{
//Tables
string[] tableStatements = GetScriptStatements(File.ReadAllText(Server.MapPath(SCRIPT_TABLES), new System.Text.UTF8Encoding()));
ExecuteStatements(tableStatements, dbName);
// Add other sql statements here... such as... !!! Notice the string array variable name is unique
// and it gets passed into the ExecuteStatements method.
//string[] newStatements = GetScriptStatements(File.ReadAllText(Server.MapPath(["some other sql scripts"]), new System.Text.UTF8Encoding()));
//ExecuteStatements(newStatements, dbName);
// Add other sql statements here... such as...
//string[] moreStatements = GetScriptStatements(File.ReadAllText(Server.MapPath(["even more sql scripts"]), new System.Text.UTF8Encoding()));
//ExecuteStatements(moreStatements, dbName);
}
Simply by adding more lines of code as shown above, you can run as many SQL scripts as you need.
IMPORTANT!!! If you run more than one sql script, each string array variable needs to be unique!
Here are the three variables in this example:
1) string[] tableStatements = GetScriptStatements(...)
2) string[] newStatements = GetScriptStatements(...)
3) string[] moreStatements = GetScriptStatements(...)
Notice that these are all unique and they get passed into the ExecuteStatements method. If you just repeat tableStatements, it will generate an error.
The next method call is the interesting one. System.Web.Management.SqlServices.Install(). What is this? It's a little known class that does essentially what "aspnet_regsql.exe" does, except you can handle these matters programmatically. For more information about this, go here, http://msdn2.microsoft.com/en-us/library/system.web.management.sqlservices.install.aspx.
if (chkTrustedConnection.Checked)
{
// For SQL Server Trusted connections
System.Web.Management.SqlServices.Install(txtServerName.Text.Trim(), ViewState["DBName"].ToString(), System.Web.Management.SqlFeatures.All);
}
else
{
// For SQL Server
System.Web.Management.SqlServices.Install(txtServerName.Text.Trim(), ViewState["DbUserName"].ToString(), ViewState["DbPassword"].ToString(), ViewState["DBName"].ToString(), System.Web.Management.SqlFeatures.All);
}
If you didn't want to use this class, you could simply gather the SQL scripts for the Membership tables and place the sql scripts in the /install/installscripts folder and call them in the RunScripts method. It will accomplish pretty much the same thing.
You'll notice there is one for SQL Server trusted connections, and one that uses all necessary information. Obviously the trusted connection overload does not require credentials.
After that, we create the roles, you can add as many roles here that you need, I'm only creating the Administrators and Users roles.
// Create the Roles, Administrators, Users
if (!Roles.RoleExists(ADMINISTRATORS_ROLE)) Roles.CreateRole(ADMINISTRATORS_ROLE);
if (!Roles.RoleExists(USERS_ROLE)) Roles.CreateRole(USERS_ROLE);
Then we create the Admin user account based on the information collected in the wizard. And then we assign the newly created admin account to the Administrators role.
// Create the Admin User
MembershipCreateStatus status = MembershipCreateStatus.UserRejected;
MembershipUser user = Membership.CreateUser(txtAdminUserName.Text.Trim(), ViewState["AdminPassword"].ToString(), txtAdminEmail.Text.Trim(),
txtSecretQuestion.Text.Trim(), txtSecretAnswer.Text.Trim(), true, out status);
// Assign the Admin user to the Administrators role
if (status == MembershipCreateStatus.Success)
{
Roles.AddUserToRole(txtAdminUserName.Text, ADMINISTRATORS_ROLE);
}
That's it!
It works pretty nicely and I hope you get some value from it.
Ok, one more thing... How do you integrate this into your own web site application?
Simple, just do the following and in seconds you can have an installer for your application:
- Copy the SetupUtility.cs file to your web site App_Code folder, or if you have a separate class library project, add the class to that project.
- Copy the Install folder in your web site.
- Create whatever database table sql scripts you need to run for your application, and add them to the /install/installscripts folder.
- Add the if statement to the Global.asax file.
- Prepare your web.config/connectionStrings section and change the connectionString attribute value to ##NOT_IMPLEMENTED##.
That should do it! This should be just a starter example for you. You can extend this any way you deem necessary for your application.
If you have any questions or find any bugs, please post them here or in my Forum.
You can download the sample web site here: DBInstallSite.zip (22 KB)
Thank you,
King Wilder











