Saturday, August 31, 2013

A generic web part to display any List or Library from any site

I have seen this question asked so many times over and over again, that it makes me think why Microsoft doesn’t provide this web part out-of-the-box. My client needed an easy way to just display few List columns on a child-site (while all other columns remain hidden), from a List available on the parent site. That should have been easy, pretty much, having the proper security in-place. But wait !! Much to my disappointments, I was stuck with every available options/solutions that I found on the www, after progressing to some extent.

Well, I know there are few that are available to choose from, but with hell lot of limitations:

-You can use the OOB Content Query web-part (CQWP). This appears to be a “hot” favorite for most people out there, but seems like you have to mess around with the xslt files to add more columns and get the things working. Yikes! my client didn’t want to be doing that every time you need to change the display view.

-You may use these code-plex solutions (this & this ) that does say about extending this CQWP and getting the job done. But I just couldn’t get that working. My bad! may be you are lucky.

-You can use the third-party Lightning Conductor web part, which was not acceptable to my client either.

So here I am. Came-up with this idea of setting up a custom web-part with custom properties to cater to my client needs. This seems pretty much generic in nature in my environment, given the site-hierarchies and security/permissions in-effect. This may not be exact same in your case and would need some re-thinking in different scenarios.

My Scenario:

-Parent site accessible only to the Finance department (who are AD users).

-Parent Site contains a List “Timecards” containing 12 different columns.

-One of its Child site “Contractors” have the security inheritance broken, and is “read-only” accessible to all AD-Users.

Client Requirement:

-All users of Finance department can see and access all 12 columns of this List “Timecards” (which is in-fact, true by default).

-All users of all departments can see “ONLY” few columns of this List “Timecards” on the child site “Contractors” which is read-only access to all.

-Site owners should be able to easily-control the specific columns to be made available for viewing on the child site.

Solution:

So what I proposed was a custom web part, that pulls the columns made available to a specific “VIEW” of the List “Timecards” containing only the needed columns on the parent site. The view can be easily maintained and modified by the site owners. In essence, this web part can actually connect to any site and pull any list data for any specified view. The data would be displayed using the SPGridView with CSS classes attached to each column for further look-n-feel changes.

Input parameters:

[SiteURL] : A custom property to specify the url to the source site hosting the List.

[ListName] : A custom property to specify the name of the source List.

[ViewName] : A custom property to specify the name of the View made available in the source List.

Implementation:

Step 1: Define Variables and properties

// Set the default values
        private string c_SiteURLDefault = "";
        private string i_ListView = "AllItems";
        private string i_ListName = "Shared%20Documents";
        private SPGridView _gridView;
        private string ROW_CSSCLASS = "nch-row-style";

// Create custom category in the property sheet and the properties as well
        [Category("Custom Properties")]
        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [DisplayName("Site URL")]
        [WebDescription("Site URL containing the List.")]
        public string SiteURL
        {
            get { return c_SiteURLDefault; }
            set { c_SiteURLDefault = value; }
        }

        [Category("Custom Properties")]
        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [DisplayName("List Name")]
        [WebDescription("Name of the List.")]
        public string ListName
        {
            get { return i_ListName; }
            set { i_ListName = value; }
        }

        [Category("Custom Properties")]
        [Personalizable(PersonalizationScope.Shared)]
        [WebBrowsable(true)]
        [DisplayName("List Name")]
        [WebDescription("View Name of the List to display.")]
        public string ListView
        {
            get { return i_ListView; }
            set { i_ListView = value; }
        }

Step 2: Initialize Constructor

       public DisplayListsLibrariesWebPart()
           : base()
       {
           _gridView = null;
       }

Step 3: Define other methods

//Method to return a data table populated with all items from a list passed to it.

       private DataTable GetSource(SPList splist)
       {
           DataTable table = splist.Items.GetDataTable();

           return table;
       }

//Method to check if a List contains a specific View. Returns “True” if found else returns “False”.

       private bool HasView(SPList list, string viewName)
        {
            if (string.IsNullOrEmpty(viewName))
                return false;

            foreach (SPView view in list.Views)
                if (view.Title.ToLowerInvariant() == viewName.ToLowerInvariant())
                    return true;

            return false;
        }

Step 4: Define CreateChildControls

            LiteralControl errmsg = new LiteralControl();
            //Variable to store a list of accessible sites.
            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            //Define your CSS styles. These classes can be over-written from within CEWP codes on the page where this web-part would be deployed.
            sb.Append(@"<style type='text/css'>");
            sb.Append(@".ms-vh2-gridview{text-align:left;}");
            sb.Append(@"</style>");

            try
            {
                if (SiteURL.ToString() != "") //if Site URL is provided in the web part property.
                {
                    SPSecurity.RunWithElevatedPrivileges(delegate()
                    {
                        //Access the Site URL from the web part properties.
                        using (SPSite site = new SPSite(SiteURL.ToString()))
                        {
                            #region Open an instance of the web.

                            //Open an instance of the web.
                            using (SPWeb targetWeb = site.OpenWeb())
                            {
                                // targetWeb provided using an absolute url is now available through OpenWeb() call directly.
                                //Next, retrieve all the user-accessible sites-subsites of the "Site URL" site-collection.

                                //Check if the List exists
                                SPList myList = targetWeb.Lists.TryGetList(ListName);
                                if (myList != null) //If List exists
                                {
                                    #region Check View exists - If-Else.

                                    //List exists, now check if the List View exists.
                                    if (HasView(myList, ListView))
                                    {
                                        //View exists, get the view.
                                        SPView listView = myList.Views[ListView];

                                        //Get the columns of the view
                                        SPViewFieldCollection viewfieldCollection = listView.ViewFields;

                                        DataTable table = GetSource(myList); //Get the List Items in a datatable.

                                        //if data-table returns rows.
                                        if (table != null)
                                        {
                                            _gridView = new SPGridView(); //Instantiate grid
                                            _gridView.ID = "_gridView";
                                            _gridView.AutoGenerateColumns = false;
                                            _gridView.Width = new Unit(100, UnitType.Pixel);
                                            _gridView.DataSource = table.DefaultView;

                                            //Set Row Alternating back color style
                                            _gridView.CssClass = ROW_CSSCLASS;

                                            string sColor = "#eeeeee";
                                            Int32 iColorInt = Convert.ToInt32(sColor.Substring(1), 16);
                                            Color alternatingRowStyleColor = System.Drawing.Color.FromArgb(iColorInt);
                                            _gridView.AlternatingRowStyle.BackColor = alternatingRowStyleColor;
                                            _gridView.EnableViewState = false;


                                            #region Get data from the list view.

                                            // Get data from the list view.
                                            SPListItemCollection items = myList.GetItems(listView);

                                            //Display each column values for each rows
                                            foreach (string viewFieldName in viewfieldCollection)
                                            {
                                                SPField columnDet = myList.Fields.GetFieldByInternalName(viewFieldName);

                                                _gridView.Columns.Add(new SPBoundField { DataField = viewFieldName.ToString(), HeaderText = columnDet.ToString() });

                                            }

                                            _gridView.DataBind();
                                        }
                                        else
                                        {
                                            //Data in the list doesnot exist.
                                            errmsg.Text = "No data found in the Source List.";
                                            throw new Exception(errmsg.Text.ToString());
                                        }

                                        #endregion
                                    }
                                    else
                                    {
                                        //View doesnot exist.
                                        errmsg.Text = "List View parameter invalid or cannot be empty. Please provide the correct value by editing the web part custom properties.";
                                        throw new Exception(errmsg.Text.ToString());
                                    }

                                    #endregion
                                }
                                else
                                {
                                    //Invalid List name. Throw exception.
                                    errmsg.Text = "Site URL or List Name parameter(s) invalid or cannot be empty. Please provide the correct value by editing the web part custom properties.";
                                    throw new Exception(errmsg.Text.ToString());
                                }
                            }

                            #endregion
                        }
                    });
                }
                else
                {
                    errmsg.Text = "Site URL parameter invalid or cannot be empty. Please provide the correct value by editing the web part custom properties.";
                    throw new Exception(errmsg.Text.ToString());
                }
            }
            catch (Exception ex)
            {
                //Display the error message.
                errmsg.Text = ex.Message.ToString();
                this.Controls.Add(errmsg);
                return;
            }

            //Add the styles for the gridview.
            LiteralControl mystyles = new LiteralControl();
            mystyles.Text = sb.ToString();
            this.Controls.Add(mystyles);

            //Display the list gridview.
            this.Controls.Add(_gridView);
            this.Controls.Add(new Literal { Text = "<hr/>" });

Step 5: Compile, Build & Deploy !!! If needed activate the feature, add the web part to the page and set the custom properties. You get a clean sharepoint like UI displaying the list rows.

Thanks for going through this entire long post !