Sign An Assembly In Visual Studio 2003

I needed to create a custom web part using Visual Studio 2003 for a SharePoint 2003 client intranet, something I have never done before. As fellow SharePoint developers will know, you need to strongly name your project assembly whenever you need deploy a custom made web part.

Visual Studio 2005 and onwards allows you to easily create a strongly named key through the graphical user interface. In Visual Studio 2003, you will need to use the Visual Studio 2003 Command Prompt. You can find the Visual Studio 2003 Command Prompt by going to: Start > Program Files > Visual Studio 2003 > Visual Studio.NET Tools > Visual Studio .NET 2003 Command Prompt.

Visual Studio 2003 Command Prompt

Once the command prompt window has opened all you need to do is enter the following to create a SNK:

Setting environment for using Microsoft Visual Studio .NET 2003 tools.
(If you have another version of Visual Studio or Visual C++ installed and wish
to use its tools from the command line, run vcvars32.bat for that version.)
 
C:\Documents and Settings\Administrator\Desktop>sn -k MyKey.snk
 
Microsoft (R) .NET Framework Strong Name Utility  Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. 
 
Key pair written to MyKey.snk
 
C:\Documents and Settings\Administrator\Desktop>

As you can see from my command prompt example above, I am creating a new SNK file called MyKey.snk. This SNK file will be generated on my Desktop.

Dynamically Load ASP.NET UserControls

I had created some .NET UserControl’s that I needed to dynamically add to a Panel control within my page. I previously thought generating my UserControl’s dynamically would be the same as dynamically generating any other .NET Control, like this:

private void CreateControls()
{
    //Create control
    TextBox txtUser = new TextBox();
    txtUser.ID = "txtUser";
    txtUser.Text = "Please enter a value";
 
    //Add Control to Panel already in our .NET page
    pnlControlPlaceHolder.Controls.Add(txtUser);
}

But I was wrong! :-)

Fortunately, there is a really easy way to to add a UserControl dynamically by simply making use of the “LoadControl” method. The “LoadControl” method takes a single parameter containing the virtual path of your UserControl. For example:

private void CreateControls()
{
    //Create control
    Control myUserControl = LoadControl("MyUserControl.ascx") as MyUserControl;
    myUserControl.ID = "ucMyControl";
 
    //Add Control to Panel already in our .NET page
    pnlControlPlaceHolder.Controls.Add(myUserControl);
}

Easy!

Using Microsoft Chart In SharePoint 2007

As promised, I have been busy creating a custom web part to display Microsoft Charts within my SharePoint reporting dashboard. Before you even think of wanting to use Microsoft Chart in a SharePoint environment, there are a few boring tasks you need to carry out beforehand:

Step 1: Copy Charting Files To SharePoint

  1. Download Microsoft Chart here.
  2. Ensure the System.Web.DataVisualization.dll is in the GAC of your SharePoint server. If not, go to your Microsoft Chart installation directory (C:\Program Files\Microsoft Chart Controls\Assemblies). You will find all the dll’s here.
  3. Copy the charting images from the MS Chart project directory.

Image Directory

  1. On your SharePoint server, go to the 12 hive and create a new folder called “MicrosoftChartControls” in the Images directory.

SharePoint Image Directory

Step 2: Modify SharePoint’s Web.Config File

  1. Add “System.Web.DataVisualization” to the SafeControls section.
<SafeControls>
    ...
    ... 
    <SafeControl Assembly="System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"  Namespace="System.Web.UI.DataVisualization.Charting" TypeName="*" Safe="True" AllowRemoteDesigner="True"/>
</SafeControls>
  1. Add the “ChartImg.axd” path to httpHandlers section.
<httpHandlers>
    ...
    ...
    <add verb="*" path="ChartImg.axd" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
</httpHandlers>
  1. Add a new AppSetting after the </system.workflow.componentmodel.workflowcompiler> tag. As you can see, this AppSetting contains the URL to where our charting images are stored.
</System.Workflow.ComponentModel.WorkflowCompiler>
<appSettings>
    <add key="ChartImageHandler" value="storage=memory;timeout=20;URL=/_layouts/Images/MicrosoftChartControls/" />
</appSettings>
  1. Lastly, ensure you add your own custom web part charting control to the SafeControls section as I have done.
<SafeControl Assembly="MOSSCharts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d463a6b3aa294272" Namespace="MOSSCharts" TypeName="*" Safe="True" AllowRemoteDesigner="True" />

If you still have problems with SharePoint failing to recognise the charting API, view this MSDN Forum posting.

My Custom Chart Example – Bar Chart

I decided it would be more useful to create a charting web part that would display graphical data from a database, instead of using randomised or hard coded values. My charting web part contains the following properties:

  • Chart Title
  • Database Connection String – in the real world you wouldn’t include this property. But it just makes it so much more easier to change database connection strings.
  • Series 1 Query – will contain a SQL Query based on the results you want returned.
  • Series 1 X Data – the column data you want displayed in the Bar Chart X Axis.
  • Series 1 Y Data - the column data you want displayed in the Bar Chart Y Axis.

Chart Properties

Based on the properties above, your chart will look something like this:

Custom Chart

This is how you can create the Bar Chart web part:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;
using System.Drawing;
using System.Data;
using System.Data.Sql;
using Microsoft.SharePoint;
using System.Web.UI.WebControls.WebParts;
using System.Data.SqlClient;
using System.ComponentModel;
using Microsoft.SharePoint.WebPartPages;
using System.Web.UI.WebControls;
 
namespace MOSSCharts
{
    public class BarChart : Microsoft.SharePoint.WebPartPages.WebPart
    {
        private string chartName;
        [WebBrowsable(true), Personalizable(true)]
        [Category("Chart Details")]
        [FriendlyName("Chart Title")]
        public string ChartName
        {
            get { return chartName; }
            set { chartName = value; }
        }
 
        private string connectionString;
        [WebBrowsable(true), Personalizable(true)]
        [Category("Database Details")]
        public string ConnectionString
        {
            get { return connectionString; }
            set { connectionString = value; }
        }
 
        private string querySeries1;
        [WebBrowsable(true), Personalizable(true)]
        [Category("Series 1 Data")]
        [FriendlyName("Series 1 Query")]
        public string QuerySeries1
        {
            get { return querySeries1; }
            set { querySeries1 = value; }
        }
 
        private string xColumnDataSeries1;
        [WebBrowsable(true), Personalizable(true)]
        [Category("Series 1 Data")]
        [FriendlyName("Series 1 X Data")]
        public string XColumnDataSeries1
        {
            get { return xColumnDataSeries1; }
            set { xColumnDataSeries1 = value; }
        }
 
        private string yColumnDataSeries1;
        [WebBrowsable(true), Personalizable(true)]
        [Category("Series 1 Data")]
        [FriendlyName("Series 1 Y Data")]
        public string YColumnDataSeries1
        {
            get { return yColumnDataSeries1; }
            set { yColumnDataSeries1 = value; }
        }
 
        protected override void CreateChildControls()
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                try
                {
                    //**************************************************************
                    //Create Connection String
                    //**************************************************************
                    SqlConnection sqlConn = new SqlConnection(ConnectionString);
 
                    //**************************************************************
                    // Create Chart Control
                    //**************************************************************
                    Chart barChart = new Chart();
                    barChart.Width = 412;
                    barChart.Height = 296;
 
                    //Specify palatte to use
                    barChart.Palette = ChartColorPalette.BrightPastel;
 
                    if (!String.IsNullOrEmpty(ChartName))
                    {
                        Title t = new Title(ChartName, Docking.Top, new System.Drawing.Font("Trebuchet MS", 14, System.Drawing.FontStyle.Bold), System.Drawing.Color.FromArgb(26, 59, 105));
                        barChart.Titles.Add(t);
                    }
 
                    //Create chart area
                    barChart.ChartAreas.Add("ChartArea1");
 
                    // Show as 3D
                    barChart.ChartAreas["ChartArea1"].Area3DStyle.Enable3D = true;
 
                    // Show columns as clustered
                    barChart.ChartAreas["ChartArea1"].Area3DStyle.IsClustered = false;
 
                    // Show X axis end labels
                    barChart.ChartAreas["ChartArea1"].AxisX.LabelStyle.IsEndLabelVisible = true;
 
                    // Set rotation angles
                    barChart.ChartAreas["ChartArea1"].Area3DStyle.Inclination = 15;
                    barChart.ChartAreas["ChartArea1"].Area3DStyle.Rotation = 20;
                     
                    //**************************************************************
                    // Add series data to chart 
                    //**************************************************************
 
                    //Create Series 1 if there is data
                    if (!String.IsNullOrEmpty(XColumnDataSeries1) && !String.IsNullOrEmpty(YColumnDataSeries1) && !String.IsNullOrEmpty(QuerySeries1))
                    {
                        //Add a new series
                        barChart.Series.Add("Series1");
 
                        // Set series chart type
                        barChart.Series["Series1"].ChartType = SeriesChartType.Column;
 
                        // Draw as 3D Cylinder
                        barChart.Series["Series1"]["DrawingStyle"] = "Cylinder";
 
                        barChart.Series["Series1"].BorderColor = Color.FromArgb(26, 59, 105);
                        barChart.Series["Series1"].Color = Color.CornflowerBlue;
                        barChart.Series["Series1"].BackSecondaryColor = Color.Navy;
                        barChart.Series["Series1"].BackGradientStyle = GradientStyle.DiagonalLeft;

                        barChart.Series["Series1"]["PointWidth"] = "0.8";
 
                        SqlCommand sqlCmdSeries1 = new SqlCommand(QuerySeries1, sqlConn);
 
                        barChart.DataSource = sqlCmdSeries1;
 
                        barChart.Series["Series1"].XValueMember = XColumnDataSeries1;
                        barChart.Series["Series1"].YValueMembers = YColumnDataSeries1;

                    }
 
                    barChart.DataBind();
 
                    Controls.Add(barChart);
                }
                catch (Exception ex)
                {
                    Label lblError = new Label();
                    lblError.Text = "An error has occurred. Please ensure you have entered correct chart details";
 
                    Controls.Add(lblError);
                }
            });
        }
    }
}

The code (above) provides a basis to display a database driven chart within SharePoint. With some further tweaks, your Reporting Dashboard could look something like this:

SharePoint Report Dashboard

If you would like the code for some of my other charts displayed within my Reporting Dashboard, please leave a comment.

Free Chart Control. About time!

I am sure all developers in the past have attempted to create their own bar or pie chart using .NET’s System.Drawing methods. However, I never been able to match a high quality finish similar to what you would expect from third party charting software. You can see my charting attempt in my post: Outputting Custom Made Charts.

As much as I would like to use .NET charting solutions from the likes of DotNetCharting and FusionCharts, they are just too expensive to buy. I have downloaded trial versions and been thoroughly impressed with their range of charts and functionality.

But it seems I have been quite late in noticing Microsoft have released their own range of chart controls to easily create rich professional looking visual data. Having had a play around in Microsoft Charts earlier this week, I have to say I am a big fan. Its a shame I hadn’t found it earlier.

From looking around that net, there are many articles and blogs on how to use Microsoft Chart so I am not going to write another. Ha! But in the next day or two, I plan to blog on how I implemented Microsoft Chart as a web part within my Report Center in SharePoint 2007. So watch this space.

In the meantime here are some useful links I have used to get started in Microsoft Chart:

Change SharePoint 2007 My Site URL

A little while back I renamed all URL’s within my SharePoint 2007 virtual environment. You can view my posting here. When it came to viewing users My Site, the new URL did not update after making changes to the Alternate Access Mappings within Central Administration.

So if you have the same problem or if you just need to change your My Site URL, here is a tutorial on how to fix this problem:

  1. Go to “Alternate Access Mappings” in SharePoint Central Administration > Operations. Click on the URL that corresponds to your My Site. This will take to you to the “Edit Internal URLs” page, allowing you to modify the My Site URL.

Alternate AccessMappings

Edit Internal Urls

  1. Go to “My Site Settings” which can be found in the Shared Service you configured in Shared Services Administration. Again, enter your new My Site URL.

My Site Settings

  1. Modify your IIS site host header value to match the URL your entered in Central Administration.
  2. Carry out an iisreset.

Cannot View SharePoint Intranet in Internet Explorer, Firefox is fine

I created a SharePoint 2007 installation on a Development Virtual Server. The installation and configuration of SharePoint was no problem. It actually went quite smoothly compared to my previous attempts. Lucky me! I thought to myself: “Man, things can’t get better than this”.

But I then encountered a small hitch. For some reason, I could not view my intranet through Internet Explorer. The login popup box kept of appearing even though my user credentials were correct. I had no problem accessing my Intranet in Firefox. As much as I love to use Firefox (because it is such an awesome browser), some SharePoint features are restricted when a non-IE browser is used.

The first thing I did was to add my SharePoint intranet URL to my Local Intranet trusted sites in Internet Explorer settings. From looking on the Internet, this has worked for some SharePoint developers. However, this did not fix my problem.

Add to trusted sites

This confirmed that Internet Explorer is not passing my login credentials to Active Directory causing problems when it came to authentication. I started snooping around Internet Information Services and viewed the Authentication Settings: Directory Security tab > Authentication and Access Control > Edit.

I changed my authentication in IIS for all my intranet web sites: Central Administration, Main Portal and My Site. By default, the IIS Authentication methods were set to Enable Anonymous Access and Integrated Windows Authentication. I removed these options and just selected: Basic Authentication.

Authentication Methods

After you have changed these settings just carry out an iisreset.

Custom SharePoint 2007 Bulk Document Uploader

I have noticed that one of many reasons clients like to get on to the SharePoint bandwagon is to use its detailed Document Management features to control the life cycle of each individual document within their organisation.

This got me thinking. Most organisations have hundreds, if not thousands of documents they would like to move from their networked storage devices to the SharePoint 2007 platform. It would be time consuming to upload all these documents to a document library. So I decided to create a C# application that would allow me to upload multiple files from a folder on a PC to a document library web part of my choice.

Just to note, this program I created has not been tested to upload documents in their thousands. I have tested uploading over 100 documents successfully. But feel free to modify my code to work more efficiently! ;-)

SharePoint Document Uploader

As you can see from my program above, I have managed to upload numerous documents from “Z:\My Received Files” to a document library called “Shared Documents”.

SharePoint Document Library

I created my program by using the following code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.SharePoint;
using System.IO; 
 
namespace MOSSDocumentLibraryUploader
{
    public partial class frmHome : Form
    {
        public frmHome()
        {
            InitializeComponent();
        }
  
        private void frmHome_Load(object sender, EventArgs e)
        {
            ddlDocumentLibList.Enabled = false;
            ddlSubSites.Enabled = false;
        }
 
        private void btnStartUpload_Click(object sender, EventArgs e)
        {
            lstUploadedDocuments.Items.Add(String.Format("Upload operation started at {0} {1}", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()));
 
            //Start uploading files
            try
            {
                //Get site collection, website and document library informationn
                SPSite intranetSite = new SPSite(txtIntranetUrl.Text);
                SPWeb intranetWeb = intranetSite.AllWebs[ddlSubSites.SelectedItem.ToString()];
                SPList documentLibrary = intranetWeb.Lists[ddlDocumentLibList.SelectedItem.ToString()];
 
                intranetWeb.AllowUnsafeUpdates = true;
                intranetWeb.Lists.IncludeRootFolder = true;
 
                //Start iterating through all files in you local directory
                string[] fileEntries = Directory.GetFiles(txtDocumentDirectory.Text);
 
                foreach (string filePath in fileEntries)
                {
                    SPFile file;
 
                    //Get file information
                    FileInfo fInfo = new FileInfo(filePath);
 
                    Stream fileStream = new FileStream(filePath, FileMode.Open);
 
                    //Load contents into a byte array
                    Byte[] contents = new Byte[fInfo.Length];
 
                    fileStream.Read(contents, 0, (int)fInfo.Length);
                    fileStream.Close();
 
                    //Upload file to SharePoint Document library
                    file = intranetWeb.Files.Add(String.Format("{0}/{1}/{2}", intranetWeb.Url, documentLibrary.Title, fInfo.Name), contents);
                    file.Update();
 
                    lstUploadedDocuments.Items.Add(String.Format("Successfully uploaded: {0}", fInfo.Name));
 
                    lstUploadedDocuments.Refresh();
                }
 
                //Perform clean up
                intranetWeb.Dispose();
                intranetSite.Dispose();
 
                lstUploadedDocuments.Items.Add(String.Format("Operation completed at {0}", DateTime.Now.ToLongTimeString()));
            }
            catch (Exception ex)
            {
                lstUploadedDocuments.Items.Add(String.Format("Error: {0}", ex));
            }
        }
 
        private void btnCancelUpload_Click(object sender, EventArgs e)
        {
            lstUploadedDocuments.Items.Add(String.Format("Upload operation cancelled at {0} {1}", DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString()));
        }
 
        private void txtDocumentDirectory_Validating(object sender, CancelEventArgs e)
        {
            if (((TextBox)sender).Text == "")
            {
                MessageBox.Show("You must enter an upload directory.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            else if (!Directory.Exists(((TextBox)sender).Text))
            {
                MessageBox.Show("Document upload directory does not exist.", "Directory Does Not Exist", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
 
        private void txtIntranetUrl_Validating(object sender, CancelEventArgs e)
        {
            if (((TextBox)sender).Text == "")
            {
                MessageBox.Show("You must enter a intranet url.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }
 
        private void btnExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }
 
        private void ddlSubSites_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                ddlDocumentLibList.Enabled = true;
 
                //Get site collection, website and document library informationn
                SPSite intranetSite = new SPSite(txtIntranetUrl.Text);
                SPWeb intranetWeb = intranetSite.AllWebs[ddlSubSites.SelectedItem.ToString()];
 
                intranetWeb.Lists.IncludeRootFolder = true;
 
                //Iterate through all document libraries and populate ddlDocumentLibList
                foreach (SPList docList in intranetWeb.Lists)
                {
                    ddlDocumentLibList.Items.Add(docList.Title);
                }
            }
            catch
            {
                ddlDocumentLibList.Enabled = false;
            }
        }
 
        private void txtIntranetUrl_TextChanged(object sender, EventArgs e)
        {
            try
            {
                ddlSubSites.Enabled = true;
                ddlDocumentLibList.Enabled = true;
 
                //Get site collection, website and document library information
                SPSite intranetSite = new SPSite(txtIntranetUrl.Text);
 
                //Iterate through all sites and propulate ddlSubSites
                foreach (SPWeb web in intranetSite.AllWebs)
                {
                    ddlSubSites.Items.Add(web.Url.Replace(txtIntranetUrl.Text, ""));
 
                    //Iterate through child sites
                    foreach (SPWeb childSite in web.Webs)
                    {
                        ddlSubSites.Items.Add(childSite.Url.Replace(txtIntranetUrl.Text, ""));
                    }
                }
            }
            catch
            {
                ddlSubSites.Enabled = false;
                ddlDocumentLibList.Enabled = false;
            }
        }
    }
}

It would have been really cool if my program only listed Document Libraries instead of all lists within a portal site. Unfortunately, I could not find any code to get a list of type Document Library. If anyone knows how to do this, I would be grateful if you could post some code.

If you have any questions on the code or know of a better (free) solution out there, please leave a comment.

DebuggerHidden Attribute and Other Cool Debugging

Debugging a page that uses many methods from other classes can become a right pain in the neck. I find myself accidentally stepping into a method that I don’t need to debug or wanting to output specific values from my methods straight away. Being the cowboy (correction, agile) developer that I am, one of my code boffins at work showed me a two cool ways to meet my debugging needs:

1) DubuggerHidden Attribute

Using the DubuggerHidden attribute tells the Visual Studio debugger that the method is hidden from the debugging process. The simple example below, shows the DebuggerHidden attribute in use:

protected void btnDoSomething_Click(object sender, EventArgs e)
{    
    //Output random number to Textbox
    txtOutput.Text = GetNumber(1, 10).ToString();
} 
 
[DebuggerHidden]
int GetNumber(int min, int max)
{    
    System.Random random = new Random();
    return random.Next(min, max);
}

2) DebuggerDisplay Attrubute

The DebuggerDisplay attribute allows us to output variable values from a class or method to be displayed in our Visual Studio debugger. The attribute includes a single argument that is supplied as a string. The string can contain references to fields, properties and methods in the class so that the actual values from an object may be included in its description.

[DebuggerDisplay("a={a}, b={b}, ValueTotal={CalculateValues()}")]
public class AddValues
{    
    public int a { get; set; }
    public int b { get; set; }
    
    public int CalculateValues()
    {
        return a + b;
    }
}

You also have the ability to include simple expressions, like so:

[DebuggerDisplay("a={a}, b={b}, ValueTotal={CalculateValues() * 2}")]

In addition to the “DebuggerHidden” and DebuggerDisplay attributes, .NET contains some very useful attributes that modify the behaviour of a debugger. For me, they weren’t as interesting as the two debugger attributes I listed above. :-)

Back Up and Running!!!

crying-man Last week my blog was offline due to an unfortunate mishap. I won’t go into the details on what happened. I’d rather just forget.

After a lot of hard work, sweat and tears, my blog is almost back to its former glory. I was lucky enough to find a backup that was made a few months ago using BlogEngine’s BlogML export tool. Hooray!

Even though all my posts are back on display, I have unfortunately lost some user comments and ratings. It saddens me to know that I have lost this valuable information, since hearing your thoughts makes this blog a more exciting read.

There is still some work to do, but I am glad to say the difficult part is over.

Will IE6 Ever Allows Us To Use CSS3?

Will IE6 Ever Allows Us To Use CSS3 We have been using Cascading Style Sheets for many years now and it has been a god send to help break us away from table based layouts and create an aesthetically pleasing site. I was quite surprised to find out that the CSS 2.1 standard has been around for 11 years. We have moved on considerably over this duration of time and even though some recent browsers are adopting CSS3 already, why do we continue to use CSS2.1?

The only answer to this question I can think of is due to the continued use of legacy browsers such as IE6. If we look at the current browser trends (below), the amount of IE6 users have decreased over the year from 18.5% to 14.4%. So this is a decrease by around 4%, which is good but not great.

2009 IE7 IE6 IE8 Firefox Chrome Safari Opera
July 15.9% 14.4% 9.1% 47.9% 6.5% 3.3% 2.1%
June 18.7% 14.9% 7.1% 47.3% 6.0% 3.1% 2.1%
May 21.3% 14.5% 5.2% 47.7% 5.5% 3.0% 2.2%
April 23.2% 15.4% 3.5% 47.1% 4.9% 3.0% 2.2%
March 24.9% 17.0% 1.4% 46.5% 4.2% 3.1% 2.3%
February 25.4% 17.4% 0.8% 46.4% 4.0% 3.0% 2.2%
January 25.7% 18.5% 0.6% 45.5% 3.9% 3.0% 2.3%
               

(http://www.w3schools.com)

Looking back at all the sites I have created over the year, I still find myself applying workarounds to give IE6 and current browsers the same viewing experience. So 14.4% of IE6 users still has a major impact on general web design.

Generally, companies are probably to blame for the continued use of IE6. Big companies are sometimes slow to change and especially if using the web is not at the business core they don’t see any reason to move forward. In addition, they see IE6 as a more secure browser.

Can I see a final nail in the IE6 coffin sometime in the future? Unfortunately, No. Microsoft have extended their support for IE6 to 2014. So for now IE6 users still have 5 years to upgrade. Lets just hope Microsoft doesn’t make the same mistake and renew IE6 support for another 5 years after 2014!

We as users of the Internet need to move with the time. If we don’t, sites will continue to be restricted to the confines of yesteryear which is no good to anybody. Legacy browsers such as IE6 need to be phased out via end of life programmes and allow newer browsers to flourish.