M6.net is the worst host of all time

I have never experienced such a lack of service and overall ability in a 'professional' company in all of my life

Dealing with M6.net has been an utter nightmare. And I want to share, half to stop other people from using them and save them the grief, half because they piss me off so much that I want to vent, and half because I hope that their company fails as they have no business charging people for what they provide (3 halves... I know).

Where to begin? So... my site was working fine for a little while, everything was all fine and dandy. Until one day I noticed that none of my downloads were working, just sending me to another blank page. I also noticed that I could no longer make posts, edit posts, comment, rate, etc. Basically my site became an internet vegetable.

So, I contacted M6 to tell them about my predicament. Apparently they installed some anti-virus software and that was breaking many sites. Apparently locking down the App_Data folder which most asp.net projects use. So let me get this straight: They decided to put untested software onto a production server which people pay for, and admit to breaking many sites. And here is the amazing explanation I get:
We are still not having any ETA from macafee but it will get resolved soon.
Wow... thanks. Keep in mind this is about a week after I put in the initial trouble ticket (which still isn't resolved, its been over 2 weeks).

But wait! There's more! I was finally able to browse via FTP to my directory (oh, did I forget to say that FTP wasn't working either?). I noticed that all of the .zip files I had stored were now mysteriously missing. I sent in another trouble ticket explaining what happened, and this is the astoundingly thorough and useful response I get in email:
I am sorry to let you know that that files are not available on the server.
That's it, that is the whole response. We lost your files... woops :P

So this is when I decided I am done with M6, this is just getting stupid. Since I had already paid for a year of service, and I still had over 6 months left in that year, I figured it was ok to ask for my un-supplied hosting fees back. Not because I was leaving the service, but because the service I paid for was not being provided to me.
My site has basically been crippled over the past 2 weeks due to your deployment of (apparently untested) software on the server which hosts my site and I have gotten nearly zero support from M6. I have a ticket open: #KNT-488469 which is no longer being responded to.

Basically I have lost traffic and money because your service has been unacceptable. I would like my money refunded from the end of this paid month (though I feel I shouldn't have to pay for this month either) until the end of my contract with some time so I can migrate to a different host. I feel this is reasonable as you are not providing the service I payed for. Is this possible? I am trying to be reasonable, but this situation is very agitating.

Thank you
They then told me they can't because of their refund policy, then I I asked for an explanation in my case why they couldn't:
If we gave refunds in this way then there would be no reason for our clients to sign up for monthly billing as they could just cancel and receive their remaining credit anyway. As our monthly packages are at a higher per month price point than our yearly and two yearly packages this would render our pricing system useless. We apologize for the personal inconvenience this may cause.
and I replied:
But you also agreed to provide me with a service that you are not providing. That is a breach of contract is it not? I am not just a user that is trying to get my money back, I have a legitimate problem and you are not remedying it. I have been cordial and this is just poor business.
After they denied me again, I immediately filed this with the Better Business Bureau. I can at least hope to make it more of a hassle to sh#t on me that some annoying emails.

Overall, this host was horrendous. I have never experience such lack of courtesy, ability and professionalism in a 'company' (it's probably just some kid in his mom's basement). I hope this review stops at least one person from signing up for their 'service'.

M6 can rot in internet hell :)

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]



Un-compressed automated backups

Sometimes you want an uncompressed backup...

I have run into many automated backup solutions, but they all seem to want to compress my data.  A particular intranet site we have has data that I regularly need to access the backups of, and it is a pain to use backup software or compression day after day.  To fix this, I came up with a script/batch file to do the work for me.  I set it to run on a schedule and I get backups in tidy date-named folders every day with automated cleanup after a specified amount of days.  It is pretty simple and straight-forward.  Uses RoboCopy for the backups (included in the zip).  Usage is as such: 

cscript backup.wsf
   /from:"d:\inetpub\website\"
   /to:"\\backup_server\s$\baks\"
   /reportto:"email_address@some_domain.com"
   /smtpserver:"email.some_domain.com"
   /holdfor:"14"
The only things that are required are to and from.  I put up a walkthrough of setting up batch files and scheduling for a different process here if you need a refresher.


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!

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]

Quickly add and attach multiple content databases to a MOSS 2007 install

A detailed tutorial on how to use some borrowed/modified scripts for making this task very simple

These scripts were stolen from SharepointBlogs.com and have been modified just a little to get some different functionality, I want them to take 95% of the credit... the rest is MINE!  (Thanks guys!)

 

These scripts were almost exactly what we needed, but we needed each content database to have custom names.  So I twisted the code around a bit and here is a detailed tutorial of what we did. 

**IMPORTANT - do this ONLY after you have made your initial web application.

 

Make the Databases

  1. Make a new database named temp_db_builder
  2. Make a table in temp_db_builder with this:
    CREATE TABLE create_dbs(
    id INT NOT NULL PRIMARY KEY IDENTITY, 
    db_name VARCHAR(MAX)
    );
  3. Fill that table with the trailing end of your content db names- EX: WSS_Content_SomeSite : 'SomeSite' = trailing end
  4. Edit make_dbs.sql (from the zip below):
    1. Change @ServiceIdentity and @AppPoolIdentity to the proper accounts
    2. Change the X in WHILE @Number <= X to the number of entries you added in the create_dbs table
    3. Change @DataFile and @LogFile to the proper destinations
    4. That is all that is needed, change the other properties if your situation requires
  5. Run that file, or run the query text in SQL Server Management Studio
  6. Databases are made

 

Produce the Batch file:

  1. Edit the file make_db_batch_file.vbs (from the zip below):
    1. edit NUMBER_OF_DATABASES to the number of names you added in the create_dbs table above
    2. edit SQL_SERVER_NAME to your SQL Server
    3. for however many databases you specified in #1, add the names to dbArray(x) from 0 to NUMBER_OF_DATABASES - these HAVE TO match the names in the table create_dbs
    4. edit DBnameconv to what the prefix of the databases is
    5. edit SiteURL to your site url
  2. Copy that file to your Sharepoint Admin Server to C:\Add_ContentDB_Script\
  3. Go in to your command prompt and run cscript make_db_batch_file.vbs from the C:\Add_ContentDB_Script\
  4. Batch File is made

 

Attach Content Databases to Sharepoint

  1. On your Sharepoint admin server, run a command prompt as the Sharepoint admin account (runas /user:domain\username cmd)
  2. Navigate to C:\Add_ContentDB_Script\ and run AddDBAContentDB.bat

 

And you should be all done.



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