Simple Gantt Chart with Asp.net

It's very easy to make a Gantt Chart in Asp.Net, just takes a little math

Recently I was asked how to make a Gantt chart in Asp.Net.  Now I am too cheap to actually go and buy some software to do this for me, so I decided to figure it out on my own; can't be that hard right?  I will just use some strategically crafted div's to look like legitimate graphs.  Turns out it really wasn't that tough... wasn't even a lot of code!

 

First thing I did was make some sample data.  Whenever I have used a Gantt Chart in the past, it has been used to plot projects over time.  So my basic elements were:

  • Title
  • Start Date 
  • End Date

 

Now you can have endless extra crap on there, but that is the basics.  Also, instead of dates, you could easily substitute numbers, but I am going to go with dates.  I just produced a DataTable with those as my 3 DataColumns and some dummy data:

 

titlestartend
Super Important Project 6/8/2008 7/3/2008
A Project 6/3/2008 6/30/2008
Crappy Project 6/25/2008 7/3/2008
Party Project 6/13/2008 6/23/2008
Being stupid 6/28/2008 7/8/2008
Getting Hammered 6/18/2008 7/1/2008
Recovering 7/2/2008 7/5/2008

 

Now that we have the data, it is just down to the math of how we are going to get this to work.  First of all, let me explain my approach to CSS graphing: using divs and the css 'width' property can easily make you a horizontal bar graph, just stack a couple divs on top of eachother that are different widths, different backgrounds and there you go, you have some nifty graphs.  The only challenge here is that we can't just use a simple percentage.  The left side of the graph isn't necessarily on the far left side, and with width is going to be relative to where it starts... hmmm.

 

I actually drew this out on some scratch paper on my desk to make it easier to see, using a nice even number: 10.


Now with this information, we can figure out how to render each div (graph line).  Since I am working with DateTime variables, I can easily convert this into TimeSpans and then Days which is an int... then it's easy math.  Notice I added in the *100 up above, that is so I am working with full percentages instead of decimals, like css uses.  Here is the code I cam up with for defining where to start a div and how wide for it to be.  Here is my function that takes in title String, start DateTime and end DataTime  and returns a nicely formatted div.  I am also using the global variables dtMin, and dtMax which are DateTime variables that are the maximum and minimum dates in the data (I use LINQ to find that easily, you can see in the code).  dateSpan is another global variable, an int that is simply (dtMax - dtMin).Days.

 

[code:c#]

public string doGantt(object t, object s, object e)
{
    string title = (string)t;
    DateTime start = (DateTime)s;
    DateTime end = (DateTime)e;

    int numberOfDays = (end - start).Days;
    int startDivAt = (start - dtMin).Days > 0 ? (start - dtMin).Days * 100 / dateSpan : 0;
    int howWide = end != dtMax ? ((end - start).Days + 1) * 100 / dateSpan : (end - start).Days * 100 / dateSpan;
    count++;
    return "<div class=\"gantt\" style=\"background:" + colors[count%colors.Count()] + ";width:" + howWide + "%;margin-left:" + startDivAt + "%;\">&nbsp;&nbsp;"+title+"</div>";
}

[/code]

 

Notice in there I call an array I had declared called colors[] which simply has the colors {"navy","maroon","orange"} in it - this simply rotates the colors of the graph.  The code is pretty straight forward I think; had to do a little magic to prevent it from falling off the edge of the page.  Then to implement it, you just need to add in a repeater in your aspx:

 

[code:html]

<fieldset>
    <legend>Gantt Chart</legend>
    <asp:Repeater ID="rpt" runat="server">
        <HeaderTemplate>
            <div style="font-weight:bold;font-size:1.2em"><%= dateHeader %><br /></div>
        </HeaderTemplate>
        <ItemTemplate>
            <%# doGantt(Eval("title"), Eval("start"), Eval("end")) %></td>
        </ItemTemplate>
    </asp:Repeater>
</fieldset>

[/code]

 

And then you set your DataSource and DataBind and you have a Gantt Chart...

 

rpt.DataSource = dt;
rpt.DataBind();

 

It even resizes liquidly to the size of the container. It is very vanilla, but definitely a good start to a simple, no images Gantt chart that could be expanded/spruced up very easily!

 


Adding Multiple Persistent Controls is Asp.Net

It's easy to add one control in asp.net during runtime, but it's not so straight-forward to add a bunch dynamically

You would think to add more and more controls, you could simply do something like this on a button click event (I will be using LiteralControls for ease of explanation, but this works the same for all controls, even custom ones):

 

[code:c#]

somePanel.Controls.Add(new LiteralControl("<div>I AM A NEW CONTROL</div>"));

[/code]

 

Which does work perfectly, and does exactly what you think it would do.  But, if you click it again, guess what?  It just re-makes the same control, replacing the old one.  Doing this over and over.  This is because what is produced is not kept in any sort of persistent state.  You need to 'tell' your program what you want it to keep.  For this,  I like to use Session variables... and if you don't like that, then too bad!

 

With that said, we will need some sort of way to keep all the controls in an easily accessable structure.  My choice here is a Generic List as they are so easy to work with and offer so much that you can do do/with them.  To start with, I will be working with a basic List<LiteralControl> but I will get on later to a list with multiple controls contained within it as it is likely that you will want to add multiple controls at each click.

 

Now we just need to show how this is going to be persistant.  First, we need to declare a list in the class.  Then, each time a control is added, it must be pushed into the list as well as the page as to keep a 'copy' of it.  But that still will not survice a postback.  We also need to put the List into a Session variable.  And now that we have that list of controls in the session, we need to check every Page_Load to see if there is any controls we have stowed away, and if so, push them to the page.  Here is the most basic way to show this with functioning code:

 

aspx 

<asp:Button ID="btn" runat="server" Text="Add Control" onclick="btn_Click" />
<asp:Panel ID="pnl" runat="server" />

 

code-behind (c#)

[code:c#]

List<LiteralControl> persistControls = new List<LiteralControl>();
protected void Page_Load(object sender, EventArgs e)
{
    // if you already have some controls populated
    if (Session["persistControls"] != null)
    {
        // pull them out of the session
        persistControls = (List<LiteralControl>)Session["persistControls"];
        foreach (LiteralControl lc in persistControls)
            pnl.Controls.Add(lc); // and push them back into the page
    }
}
protected void btn_Click(object sender, EventArgs e)
{
    // basic control for demo
    LiteralControl lc = new LiteralControl("<div style=\"border:solid 2px navy;padding:3px;margin:3px\">NEW CONTROL</div>");
    pnl.Controls.Add(lc);// add it to your page
    persistControls.Add(lc);// add it to the list
    Session["persistControls"] = persistControls; // put it in the session
}

[/code]

 

Now that is just a basic example to get the point across that this is actually a pretty easy concept.  Now on to a bit more functionality.  There are two basic things I would think an average use of this would require:

 

  • Ability to delete each 'set' of controls
  • Ability to add a bunch of controls each time and persist all of them

 

Turns out both of these are also quite easy given the structure we are using. 

 

Looking at the first one: Ability to delete each 'set' of controls should be no problem as we are using a generic List. What does a List have that makes this so simple?  List.Remove(List Item)  In fact, using a List makes this ridiculously easy.  Simply add a btnDelete_Click method to our code-behind, Now every time a control is added, a Button must also be added with a corresponding CommandArgument that relates to it's position in the List and add a Button.Click += new EventHandler(btnDelete_Click) so the new delete method will be called.  Since the List itself will automatically adjust it's size and move it's items dynamically we can simply use an integer to count up from 0 each time we push a control to the page.  And when a new Control is added, all that is needed is to add the List.Count to the CommandArgument.

 

Now on to: Ability to add a bunch of controls each time and persist all of them which will be even easier.  Instead of passing just one Control like above, we can pass a collection of Controls inside another control.  For this I chose Panels for the easy to work with <div> that they produce.  So now, every time you add a Control, you simply add all your Controls to a Panel, then push that Panel to the List (which is now a List<Panel> by the way).  And really that is all you need to do.  

 

With those two additions, the example code now looks like this:

 

aspx 

<asp:Button ID="btn" runat="server" Text="Add Control" onclick="btn_Click" />
<asp:Button ID="btnClear" runat="server" Text="Reset" onclick="btnClear_Click" />
<br /><br />
<asp:PlaceHolder ID="ph" runat="server" />

 

code-behind (c#) 

[code:c#]

List<Panel> persistControls = new List<Panel>();
Random rand = new Random(); // for display so we can get a simple difference in controls
protected void Page_Load(object sender, EventArgs e)
{
    // if you already have some controls populated
    if (Session["persistControls"] != null)
    {
        persistControls = (List<Panel>)Session["persistControls"]; // pull them out of the session
        int count = 0;
        foreach (Panel lc in persistControls)
        {
            lc.CssClass = "smallPanel";
            Button btn = new Button();
            btn.Click += new EventHandler(btnDelete_Click);
            btn.Text = "Delete";
            btn.CommandArgument = count.ToString();
            ph.Controls.Add(lc); // and push them back into the page
            ph.Controls.Add(btn);
            ph.Controls.Add(new LiteralControl("<br /><br />")); // for formatting
            count++;
        }
    }
}
protected void btn_Click(object sender, EventArgs e)
{
    LiteralControl lc1 = new LiteralControl("<span style=\"border:solid 2px navy;margin:3px\"> NEW CONTROL [ "+ rand.Next(1000,9999).ToString() + "] </span>");
    LiteralControl lc2 = new LiteralControl("<span style=\"border:solid 2px navy;margin:3px\"> NEW CONTROL [ " + rand.Next(1000, 9999).ToString() + "] </span>");
    Panel pnl = new Panel();

    pnl.Controls.Add(lc1);
    pnl.Controls.Add(lc2);
    Button btn = new Button();
    btn.Click += new EventHandler(btnDelete_Click);
    btn.Text = "Delete";
    btn.CommandArgument = persistControls.Count.ToString();
    ph.Controls.Add(pnl); // and push them back into the page
    persistControls.Add(pnl);// add it to the list
    ph.Controls.Add(btn);
    ph.Controls.Add(new LiteralControl("<br /><br />")); // for formatting
    Session["persistControls"] = persistControls; // put it in the session
}
protected void btnClear_Click(object sender, EventArgs e)
{
    Session["persistControls"] = null;
    Response.Redirect(Request.Url.ToString());
}
protected void btnDelete_Click(object sender, EventArgs e)
{
    int deleteThisOne = int.Parse(((Button)sender).CommandArgument);
    persistControls.Remove(persistControls[deleteThisOne]);
    Session["persistControls"] = persistControls; // put it in the session
    Response.Redirect(Request.Url.ToString());
}

[/code]

 

And that is all you need to do.  This could be cleaned up a bit and redundant code could be consolidated, but you get the idea.  This will work with any group of controls be it TextBoxes, DropDownLists or even custom Controls that you make.  I throw mine inside an UpdatePanel to make it function smoother.  Remember though, since this is using Session, high traffic or large control collection can make this a bit of a memory hog, so be careful. 

 

Here is an example and the code:

 



code:


Custom Web Part Template

You can use this web part as a starting point for all of your custom web parts

Especially since WSS 3.0 has been introduced, home-grown web parts have been more and more attractive to make.  The hardest part is figuring out how to start them. 

 

One option is to use a new class that simply inherits WebPart, that way everything is taken care of for you on the structure side.  That is great, but then the actual building of the insides of the web part become much more convoluted; such as using a writer to output all sorts of strange .net html elements and implementing everything programatically outside the designer.

 

A much more intuitive way is to use an ascx (web user control) and design it just like a webpage.  But the problem with that is the fact that it is a bit more complicated than that.  Simply inheriting WebPart does not really do you any good, it will work, but you can't change and WebPart attribute such as TitleUrl, Description, TitleIconImageUrl, etc.  The base Web Part properties.

 

The zip available below is a simple blank web part with inheritance taken care of all ready, you just start putting together your webpart like any other user control.  Then all you need is to register it in your page like any other control:

 

[code:html] 

 <%@ Register src="~/WebParts/blank.ascx" TagName="blank" TagPrefix="wp" %>

[/code]

 

And implement like you would any webpart:

 

[code:html]

 <wp:blank ID="wpBlank" runat="server" Title="Blank Web Part" TitleUrl="http://naspinski.net" TitleIconImageUrl="http://naspinski.net/pics/blogengine.ico" />

[/code]



Complete Web-Based Excel Spreadsheet Builder

Have your users make spreadsheets online, no excel needed

Now first off, this places a DataTable into the Session State, and I know some people have a problem with that... I don't care.  Now that that is out of the way, I can explain how this works.

 

It is very simple, this program runs through all of the TextBoxes and DropDownLists that you have within the input_container Panel and adds them as string columns to a DataTable dt.  Once that is done, each entry is simply added to the DataTable and rendered onto a GridView.  Then I use Matt Berseth's GridViewExportUtil to spit the spreadsheet out to the user.  Everything is taken care of real-time with no writing to the disk. Pretty simple.

 

I also included a way to edit as a normal method wouldn't work in this case, it just populates a DropDownList every time you add a new item and pops it back into the forms if you choose to edit it _but_ if you do not save, the record will be lost (there is a warning).

 

Another thing to note is that this is a drop-in-and-use application.  Everything is populated automatically, you need to do absolutely nothing to the code-behind in order to use this utility, just customize your fields in the default.aspx in the input_container and the rest of the work is done for you.  I used my method of parsing IDs of the input fields to make column names so ddlLets_Party turns in to a column "Lets Party", Last_Name becomes "Last Name", strFruit becomes "Fruit" and so on.   You could easily add another attribute as ColumnName or something like that if you please.

 

Here is the provided example: Excel Spreadsheet Builder In Action, and the code:



Update/Fix for the BlogEngine.Net Syntax Highlighter

This is so awesome, I can finally use the [code][/code] feature of BlogEngine without the annoying problems!

For those of you that use BlogEngine, you have probably noticed that the [code] sytnax highlighter is extremely fickle and difficult to use.  For me, I could never get the code to format without actually seeing '[code]' on my site.  I was notified by Rickard Nilsson that a new version was out and available at CodePlex.  This is fantastic, all I can say is thank you!

Cascading DropDowns using XML and LINQ

A simple alternative method to the Ajax Control Toolkit's

Don't get me wrong, the Ajax Control Toolkit (ACT) is great, but a couple of the controls leave something to be desired.  For one, the things required to integrate the cascading dropdowns is a bit excessive IMO.  No need ot require asmx files to do a simple lookup.  Also, if you are changing values in your dropdowns (as I needed to do), this gives you no simple way to do it.

 

Figuring that dropdowns are almost always relatively small collections of choices, and I would be editing them often for my purposes, an XML files seems to be the perfect fit for this situation.  On top of that, with the ease of LINQ, this would be extremely easy to make an interface for editing the properties so my users could edit them without knowing the first thing about XML. 

 

I am going to try to basically copy the ACT in terms of functionality as I really like it, but change it 'under the hood'.  First thing is to make a simple XML file with my options in it, I used old cars for an example:

<?xml version="1.0" encoding="utf-8" ?>
<dropdowns>
    <makes>
        <maker>-please select-</maker>
        <maker>Ford</maker>
        <maker>Chevy</maker>
        <maker>Dodge</maker>
    </makes>
    <cars>
        <car_maker name="Ford">
            <car>Bronco</car>
            <car>Galaxy</car>
            <car>Mustang</car>
        </car_maker>
        <car_maker name="Chevy">
            <car>Chevelle</car>
            <car>Corvette</car>
            <car>Nova</car>
        </car_maker>
        <car_maker name="Dodge">
            <car>Charger</car>
            <car>Ram</car>
        </car_maker>
    </cars>
</dropdowns>

 Now add the Dropdowns to yoru .aspx with a few more properies added (required for this to work) the properties are:

  1. cascadeTo : The dropDown you want to cascade to
  2. cascadeCategory : The category in XML your cascade properties lie
  3. cascadeDescendant : The final level in XML where your items lie
  4. cascadeBlank : [optional] What the cascading DropDown will display when it is awaiting a selection


Here is my .aspx, Notice I added "- Select Make -" to the ddlModel DropDownList as that will help give the user some guidance and it is the same as the 'cascadeBlank' I designated:

Make:<br />
<asp:DropDownList ID="ddlMake"  runat="server" cascadeTo="ddlModel" cascadeCategory="car_maker" cascadeDescendant="car" cascadeBlank="- Select Make -"  AutoPostBack="true" onselectedindexchanged="ddlMake_SelectedIndexChanged" />
<br /><br />
Model:<br />
<asp:DropDownList ID="ddlModel" runat="server" Enabled="false"  >
    <asp:ListItem>- Select Make -</asp:ListItem>
</asp:DropDownList>

Next, you need to access your XML document and fill your initial menu, so make sure to add something like this to your code-behind:

XElement x;
protected void Page_Load(object sender, EventArgs e)
{
    x = XElement.Load(Server.MapPath(".") + "\\App_Data\\dropdowns.xml");
    if (!IsPostBack)
        foreach (XElement elem in x.Descendants("maker")) addDdlItems(ddlMake, elem.Value);
}

public void addDdlItems(DropDownList ddl, string value)
{
    ListItem li = new ListItem();
    li.Value = value;
    ddl.Items.Add(li);
}

Now you simply need to call cascade.fromThisDropDown from your _SelectedIndexChange event.  This way you don't need to write any code, just place the cascade.cs fil into your App_Code folder:

protected void ddlMake_SelectedIndexChanged(object sender, EventArgs e)
 { cascade.fromThisDropDown(this.Page, (DropDownList)sender, x); }

And that's it.  You can place it inside an UpdatePanel for the same exact Ajax effect that you have with the ACT, but without those pesky asmx files. If you want to see the cascade.cs code, click here to view/hide.

 

Not completely dummy proof, but I think it's useful.  You can download just the cascade class, or the full working example:






 

Never write another RequiredFieldValidator again

A time saving class that writes them all for you

I don't know about you, but I seem to write RequiredFieldValidators (referred to as RFVs from here on out) on pretty much every project I work on.  You just can't count on the user to fill stuff out unless you make them do it.  On a recent project, I was going to have to write a TON of RFVs, and to top if off, this form was likely going to change a lot, so I didn't want to have to re-write them later over-and-over again.  So I figured out a way for .Net to do the work for me... isn't that the point of programming?  As long as you follow good naming conventions, all you need to do is call the function and blam, you have RFVs.

 

Generally, I just use RFVs for DropDownLists and TextBoxes (DDLs and TBs from here on out), so I came up with a class that finds DDLs and TBs and makes RFVs for them.  BUT, I soon realized that I don't want to have everything be required, so I made the class look for only those with '_req' (required) on the end of their ID property.  So, a TB with ID="txtFirst_Name" will not get a validator, but one with ID="txtFirst_Name_req" will.

 

With that out of the way, I wanted it to give you somewhat readable and clear error messages, so the validators will be very descriptive and readable if you use good naming conventions.  For example, the DDL with ID="ddlCity_and_State_req" or ID="City_and_State_req" will both have a validator with the message "Must select a City and State".  As you can see, the prefixes, if present, are trimmed (which you can specify if you want, the defaults are 'txt' and 'ddl') and underscores (_) are replaced by spaces ( ).

 

The function takes in:

  • Panel pnl  - Required - The Panel you want this to run on
  • string[] trimFromFront - optional - An array of prefixes you wanted trimmed off for the ErrorMessages; default is 'ddl' and 'txt'
  • string defaultDropDownValue - optional - a string that is considered 'empty' in a DDL; default is '-'
  • string validationGroup - optional - the ValidationGroup that you want these controls to validate to; default is null

 

Examples of how to call the code (remember to pass null if you are not specifying an optional value): 

  // all defaults, but you DO have to include the HtmlForm of your page
validators.setupValidators(pnl, null, null, null);
   // defaults, but sets the ValidationGroup
validators.setupValidators(pnl, null, null, "valGroup1");
   // sets the DropDown 'empty' state to 'select'
validators.setupValidators(pnl, null, "select", null);
   // trims off prefixes 'TextBox', and 'DropDown' in the ErrorMessage
validators.setupValidators(pnl, new string[] {"TextBox","DropDown"}, null, null);

*IMPORTANT this does require a ValidationSummary control on your page, otherwise you will not see the errors, though they will be working.  This was the only way to keep the output clean as teh RFVs are added to the bottom of the page. Also, I recommend calling this in your OnInit event.

 

Here is the code if anyone wants to check it out: Show/Hide


Download just the class here:


 

 

Full working example (see it in action): 



 

kick it on DotNetKicks.com

allowing/disallowing asp:menu links based on user roles

The asp:menu control is pretty useful for making  menus and easy 'popout' links, but what if you don't want certain users to be able to navigate to some pages?

If you are using a menu control, it is likely because you want stuff to be easy.  But if you have certain links in there that you do not want a user to be able to click if they are not in certain roles, it is not that obvious what you can do to implement that.  First think you will notice is that MenuItems do not have IDs, so you can't access them in the 'normal' asp.net way.

 

What you have to do is to access them through Menu.FindItem(), and the then you can manipulate them.  But then there is the question, if they do not have IDs, how do I find the item?  The items are looked up by their Text property in a hierarchical manner.  To access things in a hierarchy, you need to first set your Menu's PathSeperator property; set this to a delimiter (I use a comma) that you will use to seperate levels.  Here is a sample menu with this implemented:

<asp:Menu ID="mnu" runat="server" PathSeparator=",">
    <Items>
        <asp:MenuItem Text="home" NavigateUrl="~/Default.aspx"  />
        <asp:MenuItem Text="A" NavigateUrl="#">
            <asp:MenuItem Text="A-1" NavigateUrl="#" />
            <asp:MenuItem Text="A-2" NavigateUrl="#" />
        </asp:MenuItem>
        <asp:MenuItem Text="B" NavigateUrl="#">
            <asp:MenuItem Text="B-1" NavigateUrl="#" />
            <asp:MenuItem Text="B-2" NavigateUrl="#" />
            <asp:MenuItem Text="B-3" NavigateUrl="#" />
        </asp:MenuItem>
    </Items>
</asp:Menu>

Once that is done, you now can simply iterate through the items (that you already put in a list, and disable those you do not want the non-admin user to have access to:

 string[] adminOnlyItems = {"A,A-1", "A,A-2", "B", "B-3"};
if (!User.IsInRole("admins"))
{
    foreach (string s in adminOnlyItems)
    {
        MenuItem mi = mnu.FindItem(s);
        mi.Enabled = false;
    }
}
Notice how the items are written out in a hierarchical string, written like this: <Level 1 Item> <delimeter> <Level 2 Item> <delimeter> <Level 3 Item> and so on with as many or as few levels as you are looking for.  Also notice that "B-3" is unnecessary in the adminOnlyItems array, as disabling "B" disables that whole path of links, anythign under "B" will now be unreachable.  Here is a working example (you must have roles enabled and an "admin" role for this to work):


Moving ASP.NET roles/membership from Test/Local to Production

ASP.NET roles/membership providers are incredibly simple and convenient, but if you move you site straight to a production environment (SQL Server, etc.), it's probably not going to work without a couple changes

Believe it or not, I had never needed to use ASP.NET roles in any large production environment.  But recently at work, that changed.  Everything was working great on my local machine, couldn't be smoother, but then I moved everything to our web server with a SQL 2005 backend... no worky.

 

When you enable roles in ASP.NET within VS/VWD it automatically makes an MDF that resides in your App_Data folder.  And most likely, your machine is not running full-fledged SQL, just Express.  Which is just fine, but when you migrate to the production environment, even if you bring the MDF with you and place it in your App_Data file, your roles will not work.

 

What VS is forgetting to tell you is that your membership provider is using an 'invisible' ConnectionString called 'LocalSqlServer' and that that connectionString uses SQL Express and Windows credentials to access it.  Which is why, in most cases, that that connectionString will not work in your production setting.  What you need to do is two things:

 

  1. Move your MDF to you production SQL Server instance
  2. Make sure your role provider references that instance by changing the connection string

 

This is very easy to do, and here is how you do it:

Move your MDF to your Production SQL

  1. Copy your ASPNETDB.mdf file from your App_Code folder to Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data on your SQL machine
  2. Open SQL Server Management Studio and connect to your database
  3. Right click on the 'Databases' folder in the navigation pane, click 'Attach...'
  4. Click the 'Add...' button and navigate to your ASPNETDB.mdf on that machine, click 'OK'
  5. Click 'OK'
  6. You now have it attached and ready to use, I recommend renaming it as the name will be very long (I renamed it simply ASPNETDB.mdf - I'm creative)

Make sure your program references the new database location

  1. Go into your web.config file for your program, locate the connectionStrings section
  2. Add the following 2 lines:
    <remove name="LocalSqlServer"/>
    <add name="LocalSqlServer" connectionString="CONNECTION_STRING_TO_MEMBERSHIP_DB" providerName="System.Data.SqlClient"/>
  3. Upload and you should be all set

The important thing here is that you cleared out the 'invisible' connectionString that .Net uses and replaces it with one that is going to run off of your newly implemented database.