Feedback web part

A while back I got a question that how would I solve "feedback feature request" for my customers case (platform was MOSS 2007). Idea is that from every page of the portal you could go and give feedback. And if you do give feedback it would be important to know that what was the page that "initiated" the feedback for the user. And even more... if that page has page contact set then feedback would be automatically sent to the contact. Of course the feedback form should look a bit different dependending of the site where user decided to give feedback. Normally there would be a lot of possibilities to solve this kind of "feature request", but if you combine all the above... I'll only got one good solution so it's easy to choose what to write about this time :-)

My solution (if you have better then let me know!)

So I decided to solve this by creating a web part. Web part could then host feedback form and have logic for the rest of the requirements. But in order to have easily changeable feedback form I thought that this web part could host user control that actually is the user interface (UI) for the form. And web part could runtime select the right form to use. So next I thought that I need to have some custom code to get the page contact from previous page and other handling code too. I didn't want to add code to the UI element since I needed to support multiple feedback forms. And if those forms are different from each other it would mean that I would need to get the values in runtime. Before going to the Visual Studio I decided to create small checklist:

  • Multiple feedback form UIs
    • No code to the UI!!!
    • Seperate the code and UI
  • Form should be easily changeable runtime
    • Decision making can't be restricted in any way and the solution could come from...
      • web part properties
      • users profile
      • site properties / some data from site
      • ...
  • Feedback should know the page that initiated the call

With that list I was ready to go! Of course I did the normal look up using search engines and found nice article that has something that I could use. It was User Control Container Web Part and it was about "SmartPart" which is web part that hosts user controls. So the basic groundwork was already set and I just started to work on the other features.

I used the Visual Studio extensions for WSS 3.0 and created new web part project. In just few clicks I had my project ready for testing. Here is the code I managed to do:

 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
 using System;using System.Runtime.InteropServices;using System.Web.UI;using System.Web.UI.WebControls.WebParts;using System.Xml.Serialization;using Microsoft.SharePoint;using Microsoft.SharePoint.WebControls;using Microsoft.SharePoint.WebPartPages;using System.Web.UI.WebControls;using System.Text;using Microsoft.SharePoint.Publishing;using Microsoft.SharePoint.Utilities;namespace Feedback_Web_Part{  [Guid("f7bf59d1-f2be-4361-8a3e-f4604875fbcc")]  public class Feedback_Web_Part : System.Web.UI.WebControls.WebParts.WebPart  {    // TODO: add property so that user can change the feedback form:    String feedbackFormUrl = "/_controltemplates/Feedback.ascx";    Control feedbackForm = null;    HiddenField feedbackLocationUrl = null;        public Feedback_Web_Part()    {    }    protected override void CreateChildControls()    {      Controls.Clear();      base.CreateChildControls();      this.ChildControlsCreated = true;      feedbackLocationUrl = new HiddenField();      if (this.Page.IsPostBack == false)      {        feedbackLocationUrl.Value = this.Page.Request.ServerVariables["HTTP_REFERER"];      }      this.Controls.Add(feedbackLocationUrl);      try      {        feedbackForm = this.Page.LoadControl(feedbackFormUrl);        Button sendButton = feedbackForm.FindControl("SendFeedbackButton") as Button;        if (sendButton != null)        {          sendButton.Click += new EventHandler(sendButton_Click);        }      }      catch (System.Exception ex)      {        feedbackForm = new LiteralControl("Couldn't load '" +           feedbackFormUrl + "'! Error message: " + ex.Message);      }      if (feedbackForm != null)      {        // Add to the Controls collection to support postback         this.Controls.Add(feedbackForm);      }    }    protected override void Render(HtmlTextWriter writer)    {      this.EnsureChildControls();      this.RenderChildren(writer);      if (feedbackForm.Visible == false)      {        // User has already submitted feedback:        writer.Write("Thank you for your feedback!");      }    }    void sendButton_Click(object sender, EventArgs e)    {      StringBuilder feedback = new StringBuilder(1024);      if (feedbackForm != null)      {        foreach (Control control in feedbackForm.Controls)        {          if (control is TextBox)          {            TextBox textBox = control as TextBox;            feedback.Append(textBox.ID.Replace("_", " ") + ": ");            feedback.Append(textBox.Text);            feedback.Append(Environment.NewLine + "<br/>");          }          else if (control is DropDownList)          {            DropDownList dropdown = control as DropDownList;            feedback.Append(dropdown.ID.Replace("_", " ") + ": ");            feedback.Append(dropdown.SelectedValue);            feedback.Append(Environment.NewLine + "<br/>");          }          else if (control is CheckBoxList)          {            CheckBoxList checkBoxList = control as CheckBoxList;            feedback.Append(checkBoxList.ID.Replace("_", " ") + ":<br/>");            foreach (ListItem item in checkBoxList.Items)            {              if (item.Selected == true)              {                feedback.Append(item.Text + " ");              }            }            feedback.Append("<br/>");          }        }      }      String referer = feedbackLocationUrl.Value;      if (String.IsNullOrEmpty(referer) == false)      {        using (SPSite site = new SPSite(referer))        {          using (SPWeb web = site.OpenWeb("/"))          {            SPListItem item = web.GetListItem(referer);            if (item != null)            {              if (PublishingPage.IsPublishingPage(item) == true)              {                PublishingPage publishingPage = PublishingPage.GetPublishingPage(item);                // TODO: send email to local contact if set                // => publishingPage.LocalContactEmail                // OR                // TODO: send email to contact if set                // => publishingPage.Contact                // OR whatever you like :-)                SPListItem feedbackListItem = web.Lists["Feedback"].Items.Add();                feedbackListItem[SPBuiltInFieldId.Title] = "Feedback from " + referer;                feedbackListItem[SPBuiltInFieldId.AssignedTo] = publishingPage.Contact;                feedbackListItem[SPBuiltInFieldId.Body] = feedback;                feedbackListItem.Update();                feedbackForm.Visible = false;              }            }          }        }      }    }  }}

And that code needs also Feedback.ascx to work:

You probably noticed that there isn't anything special about that user control. Only IDs of the controls have some special meaning. I highlighted "Send" buttons ID SendFeedbackButton because it's searched from my code (on line 46). Other IDs are used when data is pulled from the form to the feedback that is stored to the SharePoint task list. Between lines 82 and 111 I'll loop the controls and dynamically get the values to the feedback. And in order to make the feedback look more nice I'll replace underscores to spaces. You can see underscore in Feedback.ascx on line 7.

Feedback form in action

Well for the person who wants to give feedback the form looks like this (and again... just to remind you... I'm not UI designer :-):

And when user presses the Send-button the form renders like this:

So this is probably good place to explain the code on lines 36 - 41. It's used to store the HTTP_REFERER information to the page. Because if you don't store it and user presses the Send-key... you'll get the current page as referer and most likely that isn't what you want.

As always my code is fast and furious (maybe too fast sometimes?) and you should take it as example but fix all the problems that I'm ignoring :-) Like in your implementation you might want to use web part properties to control the usercontrol form. It's easy... just create property and remember to add [WebBrowsable(true)] on it. Then you're good to go.

Another important thing that I just "skipped"... is the user rights (if you DIDN'T notice it while reading the code... shame on you!!!). I just created that demo to run with the same user and that isn't probably what you want. You want probably to run that "Save" code in some other user account. For that you can use the following code:

 12345678910
  SPSecurity.RunWithElevatedPrivileges(delegate() {   using (SPSite site = new SPSite(SPControl.GetContextSite(HttpContext.Current).ID))   {     using (SPWeb web = site.OpenWeb("/"))     {       // TODO: put the magic here!     }   } });

Feedback for the page contact

From code you can easily see that the task is created and it's assigned to the page contact. Here the email the page contact gets:

And if user clicks to see the item in SharePoint:

So it's pretty obvious for the page contact that he/she has received feedback regarding the page https://demo1/Pages/Main.aspx.

This could be also used for...

...sharing functionality with SharePoint apps and ASP.NET apps!

My title is kind of self describing :-) But if you want to have code that can be used in ordinary ASP.NET applications and have that same functionality in SharePoint too... well using the above methods it would be quite easily achievable. Just create web part that hosts you user controls that do some fancy stuff like get data from backend systems or whatever you need. Just keep in mind when creating the controls that it could be hosted in different kind of environments.

It think that this story has been told. Maybe next time something out of the SharePoint circle :-)

Anyways... happy hacking!

J