10.15.2011

Birthday Import Web Part (Imports from File to SPList)

Recently we had a very urgent need to create a birthday web part in SharePoint (MOSS 2007). I Goggled for a pre-made solution out there and found some nice ones; except they were running for about $125.00 a piece. Wanting not to spend any money, I decided to write a quick tutorial on this.

Please note that the purpose of this tutorial is to get you started with SharePoint development. The way I have written the code in this web part is “quick” or a “dirty way”. When writing web parts professionally, you would want to think about handling all the exceptions, web part maintenance etc.

The Project Details:
The project needs are:
  1.  Take a text file created by HR and upload its data to SharePoint



  2. The text file contains employee DOB (mm/dd) and Name.
  3. Display whose birthday is it in any given month by querying the SharePoint


The format of the text file data is as follows:
"Last Name","First Name","MM","DD"
"Last Name","First Name","MM","DD"
"Last Name","First Name","MM","DD"
"Last Name","First Name","MM","DD"


The solution:
We will take a two-part approach to this project. Basically we will create two web parts; One for processing the data and second one for displaying the birthdays. This way we can easily control the security on different web parts and keep the code separate.
Issues/Exception:
Some employees do not want their birthdays to be published. Their records will be missing the month and day information.
The SharePoint list
We will store the birthday data in a SharePoint list. You can think of the SharePoint list as a database table. To create a list:
  1. From Site Actions > View All Site Content


  2. From All Site Content click on “Create”


  3. From the Create page, click on “Custom List” under the “Custom Lists”
  4. Give list a name. In our example we will name our list “BirthdayList”.


  5. Click on “Create” button and the list will be created.
*hint: you can have multiple sites under your SharePoint server. When you create a list, please make sure you remember under which site you are creating your list. This will come in handy when we will try to query this list. For example: Main SharePoint > SubSite > Your List.
Now we have to add columns to our list in which we will store the birthday data. Think of this as adding columns to your table.
To add a new column, go to Settings > Create Column.


In our case, we will create three columns; First-Name, Last-Name, Birth-MM, Birth-DD. First name and last name will be “single line of text”, while the Birth-MM and Birth-DD will be number.




We have our list ready and now we will create a web part to insert data into this list by reading the birthday text file.
Data Import Web Part
This web part will read the text file and will insert each row into the birthday list we just created. I am assuming your visual studio is already setup on your SharePoint server. Please see this post if you need help with setting up your environment.
  1.  Launch Visual Studio 2008.
  2. Select File --> New --> Project.
  3. Select C# under Project Type.
  4. Select Class Library under Visual Studio Installed templates.
  5. Type BirthDaySystem in the Name textbox.
  6. Select a location in your hard drive.
  7. Click on the OK Button to create the BirthDaySystem web part.



  8. Rename the Class1.cs source file to ImportProcessWP.cs
  9. Right-click on the Reference foler in the Solution Explorer and the select Add Reference...
    Add the following references by going under the .NET table and then select the namespace and click OK:
    •  System.Web
    • System.Data.LINQ
    • Microsoft.SharePoint
      (C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI\Microsoft.SharePoint.dll)



  10.  In the Solution Explorer, right-click on the Properties folder and then select Open.


  11. Select the Signing tab.
  12. Check the Sign the Assembly checkbox.
  13. Select New in the Choose a strong name file..
  14. Type a desired name in the key file name.


  15. Click on the OK button. Now, Visual Studio will sign your assembly with Strong Name whenever you compile the project
  16. Open “AssemblyInfo.cs” from properties folder.
  17. At the very end of the file enter the following code:
    [assembly: AllowPartiallyTrustedCallers]   
    And make sure to add the following namespace
    using System.Security; 



Writing the Code
Open ImportProcessWP


Replace everything with the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using System.Data;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.Web.UI.HtmlControls;
using System.ComponentModel;
using System.IO;
using System.Security.Principal;
using System.Runtime.InteropServices;
 
namespace BirthDaySystem
{
 
    public class ImportProcessWP : System.Web.UI.WebControls.WebParts.WebPart
    {
 
        public ImportProcessWP()
        {
        }
 
        protected override void CreateChildControls()
        {
 
        }
 
 
    }
}


This will be your base setup every time you create any web part. So keep this example handy.
First thing we need to know is the location of the text file. Since the file location information can change in future, it would be good idea to make file location part of web part properties. This way the location information can be changed from the SharePoint without having to re-compile the project.
File Location as a Web Part Property
Enter the following code block in your ImportProcessWP class.
 

namespace BirthDaySystem
{
 
    public class ImportProcessWP : System.Web.UI.WebControls.WebParts.WebPart
    {
 
        // Private variables
        private String _TextFilePathAndName;
 
#region TextFile Property
 
        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [Category("Setup")]
        [WebDisplayName("Text File Info")]
        [Description("Text File Path and Name from which we will read the data")]
        [DefaultValue("setup")]
        public string TextFilePathAndName
        {
            get
            {
                return _TextFilePathAndName;
            }
            set
            {
                _TextFilePathAndName = value;
            }
        }
 
#endregion
 
 
        public ImportProcessWP()
        {
            _TextFilePathAndName = "setup";
        }
 
        protected override void CreateChildControls()
        {
        }
 
    }
}
   
The code snippet inside the #region #endregion tags will allow us to display the text file settings inside the web part configuration.

I used the #region tags so that I can later collapse the code for better readability and formatting. I placed _TextFilePathAndName = "setup" inside the ImportProcessWP() function so that this variable is set to “setup” by default. This way we can check whether the property has been set in the SharePoint or not.
We would want to display the web part error or status messages to the users. To be able to do this, we will create an html division (DIV) and will place text inside it whenever needed. We will put this code inside the CreateChildControls function. The CreateChildControls() is similar to “page load event” in aspx.
    
protected override void CreateChildControls()
        {
 
            HtmlGenericControl MessageDiv = new HtmlGenericControl("div");
            MessageDiv.ID = "Messagediv";
            MessageDiv.Attributes["style"] = "width:800px; text-align:left; padding:12px;";
 
            //add text to the division through out the program 
            MessageDiv.InnerHtml = "This is a text";
 
            //at the end of program, add the division control to the web part page
            this.Controls.Add(MessageDiv);
 
        }
The SPList Variable
In our SharePoint example, we will be inserting and retrieving the data from our List. To be able to do this, we will need to define List variable. Since the list name can change in future (similar to our text file name and path), I will declare the list name as a web part property instead of hard-coding it inside the program.
  1. Add the birthday list variable to the private var section. And declare SPList variable.
        
    //global vars
            // Private variables
            private String _TextFilePathAndName, _BdayListName;
            SPList Mylist;
    

  2. Write the initialization code
      
    public ImportProcessWP()
            {
                _TextFilePathAndName = "setup";
                _BdayListName= "setup";
            }
              

  3.  Add the property block
      #region Birthday list Property
    
            [Personalizable(PersonalizationScope.Shared)]
            [WebBrowsable(true)]
            [Category("Setup")]
            [WebDisplayName("Bday List Info")]
            [Description("Bday List Name")]
            [DefaultValue("setup")]
            public string BdayListName
            {
                get
                {
                    return _BdayListName;
                }
                set
                {
                    _BdayListName = value;
                }
            }
    
    #endregion
    
      
Next we will write a code which will read the text file line by line. We will write this code as follows:
  1. Create a c# structure which will have the properties like first name, last name, Birth Month etc.
  2. Create an html button and display it to the user
  3. When the button is pressed, call an event function
  4. The function will Open and Read the file
  5. After reading each line, split the line and insert that into the structure we created in step # 1
The format of the text file data is as follows:
"Last Name","First Name","MM","DD"
"Last Name","First Name","MM","DD"
"Last Name","First Name","MM","DD"
"Last Name","First Name","MM","DD"
So let’s create a global structure in which we will save the above data.
public struct EmployeeRecord
        {
            public string FirstName;
            public string LastName;
            public string DOB_MM;
            public string DOB_DD;
        }    
Open and read the file base setup:
    
if (this.TextFilePathAndName != "setup" && this.BdayListName !="setup")
            {
                Mylist = SPContext.Current.Web.Lists[this.BdayListName];

                //code for reading and opening the file will go here


            }
            else
            {
                //display error here
                MessageDiv.InnerHtml = "Please specify file name and List name in Web Part Properties";
                
            }



            this.Controls.Add(MessageDiv);

Let’s add the button code:
 
  //code for reading and opening the file will go here
                //Submit Button
                Button btnSubmit = new Button();
                btnSubmit.ID = "btnSubmit";
                btnSubmit.Click += new EventHandler(btnSubmit_Click);
                btnSubmit.Text = "Import BirthDay Data";
                btnSubmit.Attributes["style"] = "";
                this.Controls.Add(btnSubmit);
(You can do all kinds of fancy stuff in the above code by adding html table or divisions and formatting button location. The only caveat is that unlike ASPX, in Web Part you will have to define the whole GUI in the code behind)
Now we have to write the button event handler. This will be the function which will run after the button is clicked.
Before we write the event handler, there are some other functions I want out of the way:
  1. Function to delete all the old records (in my case I am updating the while list)
  2. Function to split the text file line and return it as EmployeeRecord (our structure)
 
  //This function deletes all files in any given list that passed through the parameter
        private void DeleteAll(SPList spList)
        {

             StringBuilder sbDelete = new StringBuilder();   
            sbDelete.Append("");   
            string command = "" + spList.ID +    
                "{0}Delete";   
            foreach (SPListItem item in spList.Items)   
            {  
                sbDelete.Append(string.Format(command, item.ID.ToString()));  
            }  
            sbDelete.Append("");

            SPContext.Current.Web.ProcessBatchData(sbDelete.ToString());

        }

       //This function take a line from our text file as a paramter and returns it as EmployeeRecord struct
        public EmployeeRecord GetBirthDayRecord(String RecordLine)
        {
            RecordLine = RecordLine.Replace(",,", ",\"\",");
            string[] BdayItemsArray = RecordLine.Split(new string[] { "\",\"" }, StringSplitOptions.None);

            EmployeeRecord NewRecord;

            if (BdayItemsArray.Length > 0) NewRecord.LastName = BdayItemsArray[0].Replace("\"", "").Trim();
            else NewRecord.LastName = "";

            if (BdayItemsArray.Length > 1) NewRecord.FirstName = BdayItemsArray[1].Replace("\"", "").Trim();
            else NewRecord.FirstName = "";

            if (BdayItemsArray.Length > 2) NewRecord.DOB_MM = BdayItemsArray[2].Replace("\"", "").Trim();
            else NewRecord.DOB_MM = "";

            if (BdayItemsArray.Length > 3) NewRecord.DOB_DD = BdayItemsArray[3].Replace("\"", "").Trim();
            else NewRecord.DOB_DD = "";


            return NewRecord;
        }

And now the event handler:
 
 //This will run after the button is clicked
        protected void btnSubmit_Click(object sender, EventArgs e)
        {
                  Mylist = SPContext.Current.Web.Lists[this.BdayListName];
          
            //birthday structure variable to hold the data for each line
            EmployeeRecord ItemHandle;
            String line;
            SPListItem li;

            //Delete old records
            DeleteAll(Mylist);

            //1. open and read the file
            System.IO.StreamReader file = new System.IO.StreamReader(this.TextFilePathAndName);

            while ((line = file.ReadLine()) != null)
            {
                //Function to split each line into seperate pieces
                ItemHandle = GetBirthDayRecord(line);

                li = Mylist.Items.Add();
                li["First-Name"] = ItemHandle.FirstName;
                li["Last-Name"] = ItemHandle.LastName;
                li["Birth-MM"] = ItemHandle.DOB_MM;
                li["Birth-DD"] = ItemHandle.DOB_DD;

                //save changes
                li.Update();
            }



            //close file
            file.Close();


            //display completion message
            Label StatusLabel = new Label();
            StatusLabel.ID = "StatusLabel";
            StatusLabel.Text = "The import process is complete";
            this.Controls.Add(StatusLabel);




        }

The final Code looks like this:
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using System.Data;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;

namespace BirthDaySystem
{

    public class ImportProcessWP : System.Web.UI.WebControls.WebParts.WebPart
    {

        //global vars
        // Private variables
        private String _TextFilePathAndName, _BdayListName;
        SPList Mylist;


#region TextFile Property

        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [Category("Setup")]
        [WebDisplayName("Text File Info")]
        [Description("Text File Path and Name from which we will read the data")]
        [DefaultValue("setup")]
        public string TextFilePathAndName
        {
            get
            {
                return _TextFilePathAndName;
            }
            set
            {
                _TextFilePathAndName = value;
            }
        }

#endregion


        #region Birthday list Property

        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [Category("Setup")]
        [WebDisplayName("Bday List Info")]
        [Description("Bday List Name")]
        [DefaultValue("setup")]
        public string BdayListName
        {
            get
            {
                return _BdayListName;
            }
            set
            {
                _BdayListName = value;
            }
        }

        #endregion


        public struct EmployeeRecord
        {
            public string FirstName;
            public string LastName;
            public string DOB_MM;
            public string DOB_DD;
        }



        public ImportProcessWP()
        {
            _TextFilePathAndName = "setup";
            _BdayListName= "setup";
        }

        protected override void CreateChildControls()
        {

            HtmlGenericControl MessageDiv = new HtmlGenericControl("div");
            MessageDiv.ID = "Messagediv";
            MessageDiv.Attributes["style"] = "width:800px; text-align:left; padding:12px;";


            if (this.TextFilePathAndName != "setup" && this.BdayListName !="setup")
            {

                
                
                //Submit Button
                Button btnSubmit = new Button();
                btnSubmit.ID = "btnSubmit";
                btnSubmit.Click += new EventHandler(btnSubmit_Click);
                btnSubmit.Text = "Import BirthDay Data";
                btnSubmit.Attributes["style"] = "";
                this.Controls.Add(btnSubmit);


            }
            else
            {
                //display error here
                MessageDiv.InnerHtml = "Please specify file name and List name in Web Part Properties";
                
            }



            //at the end of program, add the division control to the web part page
            this.Controls.Add(MessageDiv);



        }


        //This will run after the button is clicked
        protected void btnSubmit_Click(object sender, EventArgs e)
        {
            Mylist = SPContext.Current.Web.Lists[this.BdayListName];
               
            //birthday structure variable to hold the data for each line
            EmployeeRecord ItemHandle;
            String line;
            SPListItem li;

            //Delete old records
            DeleteAll(Mylist);
            

            //1. open and read the file
            System.IO.StreamReader file = new System.IO.StreamReader(this.TextFilePathAndName);

            while ((line = file.ReadLine()) != null)
            {
                //Function to split each line into seperate pieces
                ItemHandle = GetBirthDayRecord(line);

                li = Mylist.Items.Add();
                li["Title"] = ItemHandle.FirstName + " " + ItemHandle.LastName;
                li["First-Name"] = ItemHandle.FirstName;
                li["Last-Name"] = ItemHandle.LastName;
                li["Birth-MM"] = ItemHandle.DOB_MM;
                li["Birth-DD"] = ItemHandle.DOB_DD;

                //save changes
                li.Update();
            }



            //close file
            file.Close();


            //display completion message
            Label StatusLabel = new Label();
            StatusLabel.ID = "StatusLabel";
            StatusLabel.Text = "The import process is complete";
            this.Controls.Add(StatusLabel);




        }


        //This function deletes all files in any given list that passed through the parameter
        private void DeleteAll(SPList spList)
        {

             StringBuilder sbDelete = new StringBuilder();   
            sbDelete.Append("");   
            string command = "" + spList.ID +    
                "{0}Delete";   
            foreach (SPListItem item in spList.Items)   
            {  
                sbDelete.Append(string.Format(command, item.ID.ToString()));  
            }  
            sbDelete.Append("");

            SPContext.Current.Web.ProcessBatchData(sbDelete.ToString());

        }



        //This function take a line from our text file as a paramter and returns it as EmployeeRecord struct
        public EmployeeRecord GetBirthDayRecord(String RecordLine)
        {
            RecordLine = RecordLine.Replace(",,", ",\"\",");
            string[] BdayItemsArray = RecordLine.Split(new string[] { "\",\"" }, StringSplitOptions.None);

            EmployeeRecord NewRecord;

            if (BdayItemsArray.Length > 0) NewRecord.LastName = BdayItemsArray[0].Replace("\"", "").Trim();
            else NewRecord.LastName = "";

            if (BdayItemsArray.Length > 1) NewRecord.FirstName = BdayItemsArray[1].Replace("\"", "").Trim();
            else NewRecord.FirstName = "";

            if (BdayItemsArray.Length > 2) NewRecord.DOB_MM = BdayItemsArray[2].Replace("\"", "").Trim();
            else NewRecord.DOB_MM = "";

            if (BdayItemsArray.Length > 3) NewRecord.DOB_DD = BdayItemsArray[3].Replace("\"", "").Trim();
            else NewRecord.DOB_DD = "";


            return NewRecord;
        }



    }
}
Deploying our web part
  1. Build the solution

  2. If no error returned by the compiler, copy the DLL from your Birthday project bin > Debug directory to the bin directory of your WSS site (C:\Inetpub\wwwroot\wss\VirtualDirectories\80\bin).





  3. Go to (C:\inetpub\wwwroot\wss\VirtualDirectories\80\) and modify the Web.Config file for the WSS site to declare the custom web part as a safe control by adding the following code within the <SafeControls> tag.
 <SafeControl Assembly="BirthDaySystem" Namespace="BirthDaySystem" TypeName="*" Safe="True" />

Now we can walk through, How to insert this web part in our SharePoint Page?
  1. Go to your root SharePoint site and from there Site Actions -> Site Settings -> Modify All Site Setting.




  2. Under “Galleries" tab click "Web Parts" link.



  3. Add a new web part, Click New menu in web part gallery Page.


  4. Select the newly added web part in the "New Parts Page" and click on "populate gallery"



  5. If the import is successful, you will see the web part in Web part gallery list.


Now we see How to insert into a SharePoint Page?
  1. Go to site under which you previously created the Birthday List. Go to on Site Actions > Create Page
  2. Create a blank web part page and name it BirthDayImportPage


  3. Once the page is created, click on "Add a Web Part"


  4. From the web part list, locate the one we just created and add.

Running the web part
If all went well in previous steps, you will see a message “Please specify file name and List name in Web Part Properties”. This is because the list name and text file location has not set yet.
  1. Click on web part title bar dropdown and select “Modify Web Part”


  2. From the web part edit properties, expand the “Setup” menu.
  3. Enter “BirthdayList” in Bday List Info
  4. Enter “C:\ASBdays.txt” in Text File Info. Be careful! In this example I am entering C:\ASBdays.txt because the text file is located on this path on my SharePoint server. So in your case, you will have to enter the path where you put the text. The text file must be on the SharePoint server.
  5. Hit Apply and the web part should be ready to use.


You should now see a button.

Click on the button and web part will run and read the file and insert those records into the list.
Once the web part is done, you will see the completion message.
Go to the BirthDayList page and you should see that all records from the text file were successfully imported.


Now in the next section, we will create our birthday display web part.

0 comments:

Post a Comment