Pages

Tuesday, 8 July 2014

Building a Better SPGridView C#

Attachment: SmartSPGridView.zip
Sometimes with SharePoint customizations, you run into a scenario where you want to do something that is “just like this OOTB thing, but a little bit different.” Sometimes, that turns out to be trivial. Sometimes it turns out to be a fair amount of work to get what you want. SPGridView is one the latter – sounds great, but takes more work than I’d like to be at feature-parity with the less generic components.
Recently, I had two different projects that wanted something that looked like a ListViewWebpart, but had a more complex data source than a simple view on a list (both involved fetching data from multiple sites within the site collection and joining it to a couple of other lists). Seems simple enough, right? Fetch the data needed from all the source lists (and/or cross-site queries via SPSiteDataQuery) into custom objects, join them together via LINQ-to-Objects, stick the results in a DataTable, display it with SPGridView, and off to the next task, right? Well, yes. It does look right, but sorting and filtering don’t work. Even when you add ObjectDataSource to the mix, there are issues with sorting and filtering getting in each other’s way.
After a bit more digging online, I found a few sources that pointed me in the right direction of what I’d need to do to make it all work:
With these, plus a bit of tinkering, I got it to work like my first client wanted. However, I had to do a lot more work than I wanted to make it happen – until the last piece was in place, it wouldn’t work quite right. Two weeks later, I had another client ask me for almost the same thing (obviously, the data involved was quite a bit different, but the basic idea was the same – custom data fed into a table that looks like a normal SharePoint one). Since I’m a heavy opponent of “reuse by copy and paste”, I thought I’d separate out the “fixes” to the SPGridView from the particulars of this implementation and build a control I could use in the future when this kind of scenario came up.
In my mind, the developer wanting to use this control should only have to provide the information relevant to the problem domain (data, columns) and leave the implementation details (wiring sorting/filtering to work and work together) up to the control. To that end, I present SmartSPGridView:
     using System;     using System.Collections.Generic;     using System.Text;     using Microsoft.SharePoint.WebControls;     using System.Web.UI.WebControls;     using System.Data;     using System.Web.UI;          namespace SPGridDemo    {         /// <summary>        /// Wraps up everything needed for automatic sorting and filtering.           /// </summary>         public class SmartSPGridView : WebControl, INamingContainer        {
            public SmartSPGridView()             {                 // initialize the ObjectDataSource and SPGridView, wire them together and hook in the needed event handlers                 DataSource = new ObjectDataSource()                 {                     SelectMethod = "SelectData",                     TypeName = this.GetType().AssemblyQualifiedName,                     ID="DS",                 };                 GridView = new SPGridView()                 {                    AllowSorting = true,                     AllowFiltering = true,                     AutoGenerateColumns = false,                     FilteredDataSourcePropertyName = "FilterExpression",                     FilteredDataSourcePropertyFormat = "{1} = '{0}'",                     ID="view",                 };                 GridView.DataSourceID = DataSource.ID;                 DataSource.ObjectCreating += new ObjectDataSourceObjectEventHandler(DataSource_ObjectCreating);                 DataSource.Filtering += new ObjectDataSourceFilteringEventHandler(DataSource_Filtering);                 GridView.Sorting += new GridViewSortEventHandler(GridView_Sorting);                 GridView.RowDataBound += new GridViewRowEventHandler(GridView_RowDataBound);             }                  public SPGridView GridView { get; private set; }             public ObjectDataSource DataSource { get; private set; }     
           /// <summary>            /// Add filter header pieces             /// </summary>             void GridView_RowDataBound(object sender, GridViewRowEventArgs e)             {                 if (sender == null || e.Row.RowType != DataControlRowType.Header)                 {                     return;                 }                      SPGridView grid = sender as SPGridView;                     if (String.IsNullOrEmpty(grid.FilterFieldName))                 {                     return;                 }                      // Show icon on filtered column                 for (int i = 0; i < grid.Columns.Count; i++)                 {                     DataControlField field = grid.Columns[i];                          if (field.SortExpression == grid.FilterFieldName)                     {                         Image filterIcon = new Image();                         filterIcon.ImageUrl = "/_layouts/images/filter.gif";                        filterIcon.Style[HtmlTextWriterStyle.MarginLeft] = "2px";                              // If we simply add the image to the header cell it will                         // be placed in front of the title, which is not how it                         // looks in standard SharePoint. We fix this by the code                         // below.                         Literal headerText = new Literal();                         headerText.Text = field.HeaderText;                              PlaceHolder panel = new PlaceHolder();                         panel.Controls.Add(headerText);                         panel.Controls.Add(filterIcon);                              e.Row.Cells[i].Controls[0].Controls.Add(panel);                              break;                     }                 }             }     
            void GridView_Sorting(object sender, GridViewSortEventArgs e)             {                 // sorting loses the FilterExpression, so we have to restore it                 if (ViewState["FilterExpression"] != null)                 {                    DataSource.FilterExpression = (string)ViewState["FilterExpression"];                }            }                void DataSource_Filtering(object sender, ObjectDataSourceFilteringEventArgs e)            {                // save the filter expression built when we add a filter, so we can restore it when sort blows it away                ViewState["FilterExpression"] = ((ObjectDataSourceView)sender).FilterExpression;            }               protected override void LoadViewState(object savedState)            {                base.LoadViewState(savedState);                   // clear the saved filter so we don't restore it if the user sorts                if (Context.Request.Form["__EVENTARGUMENT"] != null &&                    Context.Request.Form["__EVENTARGUMENT"].EndsWith("__ClearFilter__"))                {                    // Clear FilterExpression                    ViewState.Remove("FilterExpression");                }            }    
           void DataSource_ObjectCreating(object sender, ObjectDataSourceEventArgs e)            {                // called by the ObjectDataSource to create an instance of this object                e.ObjectInstance = this;           }                protected override void CreateChildControls()            {                // add the ObjectDataSource and SPGridView as children               base.CreateChildControls();                this.Controls.Add(DataSource);                this.Controls.Add(GridView);            }                protected override void OnLoad(EventArgs e)            {                base.OnLoad(e);                this.EnsureChildControls();            }                public DataTable SelectData()            {                var e = new GetDataEventArgs();               OnGetData(e);                return e.DataTable;            }    
           #region GetData event+args            public class GetDataEventArgs : EventArgs            {                public DataTable DataTable { get; set; }            }            public event EventHandler<GetDataEventArgs> GetData;            protected virtual void OnGetData(GetDataEventArgs e)            {                EventHandler<GetDataEventArgs> eh = GetData;               if (null != eh)                {                    eh(this, e);                }            }            #endregion        }    }

Basically, it’s a UserControl with an ObjectDataSource and SPGridView wired together and with the events needed to get full sorting/filtering working already in place. To use this, all you have to do in your webpart is:
  • Create an instance of SmartSPGridView
  • Hook the GetData event and supply the DataTable
  • Call DataBind() at the appropriate time (OnPreRender works well)
  • Configure and add the view to your webpart (usually in CreateChildControls)
    • Add the columns
    • Populate the FilterDataFields property
    • Add the view to the webpart’s Controls collection
ObjectDataSource makes sorting work, FilterDataFields makes filtering work, and several event handlers keep them from tripping over each other. And, I don’t have to keep fighting the same battle every time I need this kind of solution for a project. Now, I’m off to pick another fight with Sharepoint.
The downloadable version of the code is linked below.

Attempted to use an object that has ceased to exist in Sharepoint

In one of the pages of my SharePoint application, I added a Links web part (web part that shows the contents of the a links list). Pretty simple. Then, I se tthe "XSL Link" property so that the results were customised in a certain style. Then I got this really strange error:
Attempted to use an object that has ceased to exist. (Exception from HRESULT: 0x80030102 (STG_E_REVERTED)) 
After some searching I understood it meant that the current SPContext could not be accessed. I then realised that another (custom) web part in the page was destroying the SPContext object after use, so when the Links web part (OOTB) tried to use it, it couldn't.
Look for anything like
(SPWeb site SPContext.Current.Site) {...}

in your custom web part code. This is wrong. You must not dispose the current site object. Instead you can use something like:
using (SPWeb elevatedSite = elevatedsiteColl.OpenWeb(siteID)) { ... }
Guid siteID = SPContext.Current.Web.ID;


Friday, 21 March 2014

Send GridView in Mail throughSharepoint

This example makes u enable to understand how to email server controls information like Gridview as it is.
Now I am considering that we have a web form which contains a GridView (containing Data) and a button
which will be used to send email.

At first include these namespaces to your code behind.

using System.Net.Mail;
using System.Text;
using System.IO;

Now in Button Click event write this code :


protected void ibMail_Click(object sender, ImageClickEventArgs e)
    {
       //Get the Sharepoint SMTP information from the SPAdministrationWebApplication  
                SPUser sUser = _SPHelper.CurrentWeb.CurrentUser;
                string FromEmailAddress = sUser.Email;
                string smtpServer = SPAdministrationWebApplication.Local.OutboundMailServiceInstance.Server.Address;
                string smtpFrom = false ? SPAdministrationWebApplication.Local.OutboundMailSenderAddress : FromEmailAddress;
                string subject = "GridView Details";
                string Body = "Dear Sir/Madam ,<br> Plz Check the gridview details <br><br>";
                Body += GetGridviewData(Grid1); //Elaborate this function detail later
                Body += "<br><br>Regards,<br>" + sUser.Name;
                bool send = send_mail("TestUser@gmail.com", smtpFrom, subject, Body, smtpServer);//Elaborate this function detail later
                if (send == true)
                {
                    ScriptManager.RegisterStartupScript(this.Page, Page.GetType(), "Insert", "alert('Mail has been sent.');location.href('" + _SPHelper.CurrentWeb.Site.Url + "');", true);
                }
                else
                {
                    ScriptManager.RegisterStartupScript(this.Page, Page.GetType(), "Insert", "alert('Problem in sending mail....Try Later...');location.href('" + _SPHelper.CurrentWeb.Site.Url + "');", true);
                }

    }

send_mail() Definition :


public bool send_mail(string to, string from, string subject, string body, string smtpServer)
        {
            System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage(from, to);
            msg.Subject = subject;
            AlternateView view;
            SmtpClient client;
            StringBuilder msgText = new StringBuilder();
            msgText.Append(" <html><body><br></body></html>" + body);
            view = AlternateView.CreateAlternateViewFromString(msgText.ToString(), null, "text/html");

            msg.AlternateViews.Add(view);
            client = new SmtpClient(smtpServer);
            client.Send(msg);
            bool k = true;
            return k;
        }



GridViewToHtml() definition :


  private string GetGridviewData(RadGrid Grid1)
        {
            string GridRawHtml;
            StringWriter stringWriter = new StringWriter();
            HtmlTextWriter clearWriter = new HtmlTextWriter(stringWriter);
            Grid1.RegisterWithScriptManager = false;
           
Grid1.RenderControl(clearWriter);
            GridRawHtml = clearWriter.InnerWriter.ToString();
            GridRawHtml = GridRawHtml.Remove(GridRawHtml.IndexOf("<script"), GridRawHtml.LastIndexOf("</script>") - GridRawHtml.IndexOf("<script"));
            Response.Write(GridRawHtml);
            return GridRawHtml;
        }



Now browse and send mail and output will be like this one -:


Sometime one can  encountered by this error  -:
RegisterForEventValidation can only be called during Render();

This means that either you have forgot to override VerifyRenderingInServerForm in code behind or EventValidation is true.
So the solution is set EventValidation to false and must override VerifyRenderingInServerForm method.