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:


XML to DataTable with LINQ

Easy way to get your XML into a DataTable

Now I might just be blind, or incredibly incapable at searching google or reading my LINQ books (very possible) but I hadn't found a simple way to get a 2-level XML document into a DataTable for use in a GridView or just simple DataTable manipulation utilizing LINQ (I assume this is because DTs are 2 dimensional, and XML files can be all sorts of mash-ups of information). Since LINQ is so powerful, I assumed it wouldn't be all that difficult, turns out I was right, it's pretty easy. Here is the code:

[code:c#]

public DataTable XElementToDataTable(XElement x)
{
  DataTable dt = new DataTable();

  XElement setup = (from p in x.Descendants() select p).First();
  foreach (XElement xe in setup.Descendants()) // build your DataTable
    dt.Columns.Add(new DataColumn(xe.Name.ToString(), typeof(string))); // add columns to your dt

  var all = from p in x.Descendants(setup.Name.ToString()) select p;
  foreach (XElement xe in all)
  {
    DataRow dr = dt.NewRow();
    foreach (XElement xe2 in xe.Descendants())
      dr[xe2.Name.ToString()] = xe2.Value; //add in the values
    dt.Rows.Add(dr);
  }
  return dt;
}

[/code]


This is completely dynamic, so it doesn't matter what or how many elements you have. It does rely on the first element to set the DataTable columns, so make sure that one is complete. Though the XML does have to be limited to 2-dimensional elements; in fact, I am not even sure what happens if you feed the function bad data? The XML should resemble this structure:

<?xml version="1.0" encoding="utf-8"?>
<root>
      <person>
            <age>26</age> 
            <name>stan</name> 
            <hobbies>partying</hobbies> 
      </person>
      <person>
            <age>26</age> 
            <name>matt</name> 
            <hobbies>being lame</hobbies> 
      </person>
</root>

In that structure, each person will be a row, and age, name and hobbies will the the columns in the datatable:
agenamehobbies
26 stan partying
26 matt being lame
Call it like this:

[code:c#]

// load your xml file (this one is named people and it is in my App_Data folder)
XElement x = XElement.Load(Server.MapPath(".") + "\\App_Data\\people.xml");//get your file
// declare a new DataTable and pass your XElement to it
DataTable dt = XElementToDataTable(x);

[/code]


And that's it, you have your DataTable.


Using DefaultIfEmpty() to check if an element exists with LINQ

This is a very handy way to test if an element exists using LINQ

Often you want to run something only if an element exists. There is always the try/catch method, but that doesn't seem very elegant to me, and this gave me an excuse to figure some more out about LINQ, which I am finding I like more and more.

This is actually very simple once you understand how DefaultIfEmpty() works. It may seem a bit obvious, but it returns a default value if something is not there. The easiest way to do this is to give it something explicitly so you know what it is returning for the default; I just use a dummy instance. For this example, I am using LINQ with XML and XElements

[code:c#]

//make your dummy element
XElement dummy = new XElement("dummy");
//assign it an easy to recognize null value
dummy.Value = "Does Not Exist";
//now run a query with it
foreach (XElement p in xmlFromFile.Elements("anElement").Descendants("someElement").DefaultIfEmpty(dummy))
{
  if(process.Value.Equals("Does Not Exist")
    //it does not exist
}

[/code]

It's just that easy. Works the exact same with SQL or any other data source as well.

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:



Appending a single Excel sheet to an existing Excel Spreadsheet programatically

      

This can be done via a web-interface via Excel Interop commands

I was asked to take some user input, produce an Excel spreadsheet, then append it to an exisiting workbook that had multiple static spreadsheets then spit the Excel sheet out as a download.  Excel Interop processes are not that easy to work with, and can be quite a pain.  After fumbling around, I was able to come up with a (possibly rudimentary) way to do this.

 

Now this is just a small part of a much larger program that takes in user input, and saves it to a temporary directory as an Excel spreadsheet.  Inside that directory there is also another file that doesn't change that thsi will be appended to, I call this my 'base' file.  The snippet I am providing combines the files and saves them as a seperate new file before my program sends it to the user.

 

There is likely a better way to do this without all of the saving with a stream, but I am not sure how and am wide open for better solutions!  Anyways, here is the function I ended up using.  The inputs are:

  • baseFile is the path to the static file I am appending to
  • newSheet is the path to the new file that I am appending to the base
  • newFile is the path to the final appended sheet 

 

[code:c#]

protected void AppendSheet(string baseFile, string newSheet, string newFile)
{
    try
    {
        exc = new Microsoft.Office.Interop.Excel.Application();
        exc.Workbooks.Open(baseFile, Type.Missing, false, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
        exc.Workbooks[1].Sheets.Add(Type.Missing, exc.Workbooks[1].Sheets[1], 1, newSheet);
        if (System.IO.File.Exists(newFile)) System.IO.File.Delete(newFile);
        exc.Workbooks[1].SaveAs(newFile, Type.Missing, Type.Missing, Type.Missing, Type.Missing, false, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
    }
    catch (Exception ex)
    {
        //handle your exception
    }
}

[/code]

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:






 

Destroy those pesky orphaned Excel processes in .Net programs

If you have ever worked with Excel in the .Net environment, you likely have run across the occasional orphan and they can be a pain to clean up

Recently I was doing just this, and my Task Manager was filling up with orphans fast.  I was calling for the Excel Process to close, but that wasn't working most of the time.  So I came up with a more elaborate way to make them disappear.  Basically I found out that in order to close the application, you have to close the workbooks, and in order to close the workbooks, you have to close each workbook individually, and to close those, you have to close each worksheet in those workbooks individually.  I also included COM management, so we are hitting this with multiple attacks to make sure they stay dead!  So the super-overkill-do-everything process is:

 

  1. Collect each worksheet individually
  2. Collect each workbook individually
  3. Delete the worksheets
  4. Release the worksheets
  5. Null the worksheets
  6. Delete the workbooks
  7. Release the workbooks
  8. Null the workbooks
  9. Close the workbooks collection
  10. Quit the application
  11. Release the application
  12. Null the application
  13. Collect garbage

 

Here's how

After your work is done, you are left with a Microsoft.Office.Interop.Excel.Application named 'exc', call killExcel(exc):

protected void killExcel(Microsoft.Office.Interop.Excel.Application exc)
{
    try
    {
        List<Microsoft.Office.Interop.Excel.Workbook> wbs = new List<Microsoft.Office.Interop.Excel.Workbook>();
        List<Microsoft.Office.Interop.Excel.Worksheet> wss = new List<Microsoft.Office.Interop.Excel.Worksheet>();
        foreach (Microsoft.Office.Interop.Excel.Workbook wb in exc.Workbooks)
        {
            foreach (Microsoft.Office.Interop.Excel.Worksheet ws in wb.Worksheets)
                wss.Add(ws); // collect worksheets
            wbs.Add(wb); // collect workbooks
        }
        for (int i = 0; i < wss.Count; i++)
        {
            wss[i].Delete();
            System.Runtime.InteropServices.Marshal.ReleaseComObject(wss[i]); // release it
            wss[i] = null; // null it
        }
        for (int i = 0; i < wbs.Count; i++)
        {
            wbs[i].Close(null, null, null);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(wbs[i]); // release it
            wbs[i] = null; // null it
        }
        exc.Workbooks.Close(); // so you can close this
        exc.Quit(); // so you can quit this
        System.Runtime.InteropServices.Marshal.ReleaseComObject(exc); // release it
        exc = null;
        GC.Collect(); // this sets up the finalizers
        GC.WaitForPendingFinalizers();
        GC.Collect(); //apparently this kills it
        GC.WaitForPendingFinalizers();
    }
    catch (Exception ex)
    {
        // deal with it fool!
    }
}

Yes this is overkill, but I think I covered all possible ways to kill you processes, so they should be more dead than Elvis; no more orphans - yay for iteration!

 

Some useful links:

http://www.thescarms.com/dotnet/ExcelObject.aspx 

http://www.devcity.net/Articles/239/3/article.aspx 

http://krgreenlee.blogspot.com/...ting-excel_10.html 

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