This project is read-only.
Project Description
Microsoft offers a nice selection of out of the box filters for filtering dashboards, but they are somewhat limited when it comes to real-world use.

The Hosted User Control Feature provides a way to easily create custom filters to avoid these limitations.

SUMMARY

Hello and welcome to another edition of Developer’s Notes. In this edition, we will be discussing Filtering Web Parts for use with the SQL Server Reporting Services Viewer control in MOSS. These filters give users the ability to create dynamic dashboards of information that can be filtered based on business data. Several options are available out of the box; however, these items do not fit many of the paradigms generally used by developers, such as populating data for drop down lists based on a web service or SQL call. In addition, these filters do not offer any styling options to allow them to fit with the look and feel of the customer’s portal.

THE OUT OF THE BOX FILTERS

As mentioned in the summary, there are several out of the box filters that are available when you have the MOSS enterprise edition installed. Note, these are ONLY available when you have the Office SharePoint Server 2007 Enterprise CAL or for Internet Site. This is an important note, which we will discuss further in the custom filters section below.

Business Data Catalog Filter
This filter will provide a means to search a business data catalog for values in a typically “SharePoint” way, with a text box for input as well as an available search dialog.

Choice Data Filter
This filter provides a sort of popup dialog that allows the user to pick a single value based off of a list of strings. This is a fairly flexible setup, allowing the developer to enter in exactly what should be allowed for filtering. The down side; however is that this is a manually entered list that obviously is not going to be able to adjust to changing business data.

Current User Filter
This filter is a context based filter that will return the username of the currently logged in user.

Date Filter
This is a simple date control that takes advantage of the SharePoint calendar control to allow the user to pick a date for a report.

Page Field Filter
This filter will return the value of a published field from the page it is connected to, such as Content Type, Created, Title, etc.

Query String (URL) Filter
This filter will redirect the value from a query string parameter as a filter value. This is useful for pre-populating report filters based on URLs

SharePoint List Filter
This allows the designer to tie a popup selection window, similar to the Choice filter, to a SharePoint list. There are some limited settings available which allow the designer to pick the display and value columns from a content type in a list view.

SQL Server Analysis Services Filter
This control allows the designer to pull data from either a KPI Web Part or directly from an SSAS cube.

Text Filter
This simple text box filter allows a user to filter based on free-form text.

As you can see, this is a nice selection of items, but it is somewhat limited in scope of what many clients might want to have available for their filters. Many would want to be able to adjust the look and feel of their filtering while also providing direct data from web services and SQL. None of those options are supported by Microsoft at this time. In addition the entire feature set is dependent on the purchase of a rather expensive CAL that some customers may not be able to afford. The solution to these challenges is to build custom filters.

CUSTOM FILTERS

Microsoft developed their out of the box filters to use a base class that acts as the ancestor of all of the filters in some way. Luckily, while the base class provides some functionality that is unique to their collection of filters, it is not the driving factor that makes those filters available for filtering the report viewer. This is accomplished through the use of the ITransformableFilterValues interface, which is part of the WSS3 API. This allows us to build whatever filters we need using the freely available SharePoint API.

For the purposes of this demonstration, we will create the following custom filters:
1) Simple single value drop down filter
2) Multi-value list box
3) 2 Stage cascading drop down filter

In addition, a button is needed to apply the filter values to the Report Viewer as well as save default values for individual users (so that they can automatically view the same report every time).

Basic Framework

Another point we will cover is how to enable these controls for easy UI changes. The simplest approach for this would be to use user controls. To some this means using a customized version of the Smart Part, but it doesn’t have to be quite that complicated. Let’s start with a simple base web part that will load a user control based on a path we give it.

01 :  	[Guid("2f5a595f-8ad5-483d-b584-1ec088eedb17")]
02  :  	public class HostedUserControlFilter : Microsoft.Sharepoint.WebPartPages.WebPart
03  :  	{
04  :  	        [Personalizable(PersonalizationScope.Shared)]
05  :  	        [WebBrowsable(true)]
06  :  	        [System.ComponentModel.Category("Settings")]
07  :  	        [WebDisplayName("Path to ASCX File")]
08  :  	        [WebDescription("Give the complete (relative) URL to the user control")]
09  :  	        public string VirtualPath
10  :  	        {
11  :  	            get
12  :  	            {
13  :  	                if (virtualPath == null)
14  :  	                {
15  :  	                    virtualPath = "";
16  :  	                }
17  :  	                return virtualPath;
18  :  	            }
19  :  	            set { virtualPath = value; }
20  :  			}
21  :  	 
22  :  			UserControl localControl = null;
23  :  			protected override void CreateChildControls()
24  :  			{
25  :  				base.CreateChildControls();
26  :  				if (virtualPath != string.Empty && Page != null)
27  :  				try
28  :  				{
29  :  					localControl = Page.LoadControl(virtualPath) as UserControl;	
30  :  					this.Controls.Add(localControl as Control);
31  :  				}
32  :  				catch
33  :  				{
34  :  					string error = string.Format("Unable to load control {0}.  Verify that control exists and uses the FQDN of its base class.", virtualPath);
35  :  					this.Controls.Add(new LiteralControl(error));
36  :  				}
37  :  		}
38  :  	}


As you can see above, the web control has a VirtualPath property. This will point to the path of the User Control we would like to load. The attribute decorations on the top of the property describe how to show this setting in the SharePoint web part editor. Of course, some time could be spent building a tool part that could provide a drop down of valid User Controls based on a path, but this simple textbox based approach will work for now. Once the virtual path is set, the control will load up into the web part as desired.

This takes care of the presentation part of the web control. The next step is to wire our web part to act as a filter. There are two steps to this process:

1) Implement the ITransformableFilterValues interface.
2) Expose a Connection method.

Implementing the ITransformableFilterValues interface

The ITransformableFilterValues interface is structured as show below:
01  :  	namespace Microsoft.SharePoint.WebPartPages
02  :  	{
03  :  	    public interface ITransformableFilterValues
04  :  	    {
05  :  	        bool AllowAllValue { get; }
06  :  	        bool AllowEmptyValue { get; }
07  :  	        bool AllowMultipleValues { get; }
08  :  	        string ParameterName { get; }
09  :  	        ReadOnlyCollection<string> ParameterValues { get; }
10  :  	    }
11  :  	}


As in all interfaces, the functionality is in the eye of the beholder, so to speak. In the case of the ReportViewer control, the important functionality of this interface is the ParameterName and ParameterValues properties. The rest are set to false.

So when implementing this interface in our HostedUserControlFilter web part, the control looks as shown:

01  :  	[Guid("2f5a595f-8ad5-483d-b584-1ec088eedb17")]
02  :  	public class HostedUserControlFilter : Microsoft.Sharepoint.WebPartPages.WebPart
03  :  	{
04  :  	        [Personalizable(PersonalizationScope.Shared)]
05  :  	        [WebBrowsable(true)]
06  :  	        [System.ComponentModel.Category("Settings")]
07  :  	        [WebDisplayName("Path to ASCX File")]
08  :  	        [WebDescription("Give the complete (relative) URL to the user control")]
09  :  	        public string VirtualPath
10  :  	        {
11  :  	            get
12  :  	            {
13  :  	                if (virtualPath == null)
14  :  	                {
15  :  	                    virtualPath = "";
16  :  	                }
17  :  	                return virtualPath;
18  :  	            }
19  :  	            set { virtualPath = value; }
20  :  		}
21  :  	
22  :  		UserControl localControl = null;
23  :  		protected override void CreateChildControls()
24  :  		{
25  :  			base.CreateChildControls();
26  :  			if (virtualPath != string.Empty && Page != null)
27  :  			try
28  :  			{
29  :  				localControl = Page.LoadControl(virtualPath) as UserControl;	
30  :  				this.Controls.Add(localControl as Control);
31  :  			}
32  :  			catch
33  :  			{
34  :  				string error = string.Format("Unable to load control {0}.  Verify that control exists and uses the FQDN of its base class.", virtualPath);
35  :  				this.Controls.Add(new LiteralControl(error));
36  :  			}
37  :  		}
38  :  	 
39  :  	        #region ITransformableFilterValues Members
40  :  	
41  :  	        public bool AllowAllValue {get { return false; } }
42  :  	
43  :  	        public bool AllowEmptyValue { get { return false; } }
44  :  	
45  :  	        public bool AllowMultipleValues { get { return false; } }
46  :  	
47  :  	        string parameterName = string.Empty;
48  :  	
49  :  	        [Personalizable(PersonalizationScope.Shared)]
50  :  	        [WebBrowsable(true)]
51  :  	        [System.ComponentModel.Category("Settings")]
52  :  	        [WebDisplayName("Filter Parameter Name")]
53  :  	        public string ParameterName
54  :  	        {
55  :  	            get { return parameterName; }
56  :  	            set { parameterName = value; }
57  :  	        }
58  :  	
59  :  	        public ReadOnlyCollection<string> ParameterValues
60  :  	        {
61  :  	            throw new NotImplementedException();
62  :  	        }
63  :  	
64  :  	        #endregion
65  :  	}


As you may notice, we decided to decorate the ParameterName property with the necessary Edit Pane attributes so that the user can name the property the web part represents at runtime.

At this point there is one property still to implement -- ParameterValues. This property will return a read only collection of strings that represent the values we want to return to the report viewer. This would come from our user control, but since the user control is dynamically assigned, we need to know what to return. In this case, the simplest route is to implement an interface. For the purposes of this demonstration we have created one called IHostedUserControlFilter.

01  :  	    public interface IHostedUserControlFilter
02  :  	    {
03  :  	        List<string> Values { get; set; }
04  :  	    }


One might look at this interface and think that there is no reason for a set accessor since this is a read-only kind of control, but we will indeed need a set accessor later on when we discuss saving the user’s default values.

Since we now have an interface that we want our user controls to implement, we must now go back and do some type checking when we load up our control. To accomplish this we will change the localControl member to be of type IHostedUserControlFilter and we will add a type check to our CreateChildControls method to check that the control we have loaded fits our interface.

01  :  	IHostedUserControlFilter localControl = null;
02  :  	protected override void CreateChildControls()
03  :  	{
04  :  		base.CreateChildControls();
05  :  		if (virtualPath != string.Empty && Page != null)
06  :  		try
07  :  		{
08  :  			localControl = Page.LoadControl(virtualPath) as IHostedUserControlFilter;
09  :  			if (localControl == null) throw new InvalidCastException("User Control must implement IHostedUserControlFilter");	
10  :  			this.Controls.Add(localControl as Control);
11  :  		}
12  :  		catch
13  :  		{
14  :  			string error = string.Format("Unable to load control {0}.  Verify that control exists and uses the FQDN of its base class.", virtualPath);
15  :  			this.Controls.Add(new LiteralControl(error));
16  :  		}
17  :  	}


One final step is needed to make our Hosted User Control functional as a filter. We have to implement a Connection that will allow us to tie this web part to the report viewer. We do this simply by returning the web part through a property decorated with the ConnectionProvider attribute:

01  :  	[ConnectionProvider("Filter Value", "A8A1CDC7-5C5F-4678-8FF2-EC44F65BCC76", AllowsMultipleConnections = true)]
02  :  	public ITransformableFilterValues GetConnection()
03  :  	{
04  :  		return this;
05  :  	}


The name “Filter Value” must be hard coded, so we cannot tie it to the Parameter Name set at runtime, but this will be a generic enough name. The Filter Value name is displayed in the connection menu used to trigger the connection edit screen. The Parameter Name; however, is shown in the actual connection edit screen.

Building a User Control for the filter
Now that the basic framework is out of the way, we need to build a user control to display in our filter container. Let’s start with a simple one, a single hard coded drop down menu.

01  :  	<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SimpleDropDownFilter.ascx.cs" Inherits="CustomFilters.UserControls.SimpleDropDownFilter, CustomFilters, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3302bed8990a619c" %>
02  :  	
03  :  	<asp:DropDownList ID="cboStates" runat="server">
04  :  	    <asp:ListItem Text="Texas" Value="TX" />
05  :  	    <asp:ListItem Text="Tennesee" Value="TN" />
06  :  	    <asp:ListItem Text="Oklahoma" Value="OK" />
07  :  	</asp:DropDownList>


As you can see, there is not much to the front end of this control. The code behind implements the IHostedUserControlFilter interface we defined earlier. One thing that you should note here, however, is that the Inherits attribut of the Control tag is using the fully qualified assembly name of the base class. This is because the assembly for the SharePoint solution will be going into the GAC. SharePoint then needs the FQAN to find the correct version of the base class for loading. If you do not set this in this manner, you will end up with a “Cannot load type” error.

01  :  	public partial class SimpleDropDownFilter : System.Web.UI.UserControl, IHostedUserControlFilter
02  :  	{
03  :  		#region IHostedUserControlFilter Members	
04  :  		public List<string> Values
05  :  	    	{
06  :  	    		get
07  :  			{
08  :  				return new List<string>(new string[] { cboStates.SelectedItem.Value});
09  :  			}
10  :  			set
11  :  			{
12  :  				if (value.Count > 0)
13  :  				{
14  :  					cboStates.Items.FindByValue(value[0]).Selected = true;
15  :  				}
16  :  			}
17  :  		}
18  :  		#endregion
19  :  	}


As you may notice, we implemented the set accessor here as well by simply looking for the first value in the list in the drop down list and setting it to selected. There is no other code needed for this control, given that the values are hard coded, but we could just as easily implemented a web service call or database call to fill the drop down list.


Let’s try another one. In this user control, we will be supporting multiple values through a multi-select list box control.

01  :  	<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MultiSelectControl.ascx.cs" Inherits="CustomFilters.UserControls.MultiSelectControl, CustomFilters, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3302bed8990a619c" %>
02  :  	<asp:ListBox ID="ListBox1" runat="server" SelectionMode="Multiple">
03  :  	    <asp:ListItem>Red</asp:ListItem>
04  :  	    <asp:ListItem>Green</asp:ListItem>
05  :  	    <asp:ListItem>Blue</asp:ListItem>
06  :  	</asp:ListBox>


Similar the the SimpleDropDownFilter above, we have the following code behind.
01  :  	public partial class MultiSelectControl : System.Web.UI.UserControl, IHostedUserControlFilter
02  :  	{
03  :  		#region IHostedUserControlFilter Members
04  :  		public List<string> Values
05  :  	   	{
06  :  	    		get
07  :  	        	{
08  :  	        		return ListBox1.Items.Cast<ListItem>().Where(item => item.Selected).Select(q => q.Value).ToList();
09  :  	        	}
10  :  			set
11  :  			{
12  :  				foreach (string Item in value)
13  :  				{
14  :  					ListBox1.Items.FindByValue(Item).Selected = true;
15  :  				}
16  :  			}
17  :  		}
18  :  		#endregion
19  :  	}


In this case, we use a little LINQ to convert out selected item values to a list of strings for the getter and we iterate through the inbound list of strings to set the selected values in the setter.

For our last filter, we will put together a cascading drop down control; in this example, a list of states that controls a list of cities.

01  :  	<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DependantDropDownFilter.ascx.cs"
02  :  	    Inherits="CustomFilters.UserControls.DependantDropDownFilter, CustomFilters, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3302bed8990a619c" %>
03  :  	<asp:UpdatePanel ID="UpdatePanel1" runat="server">
04  :  	    <ContentTemplate>
05  :  	        State: 
06  :  			<asp:DropDownList runat="server" AutoPostBack="True" ID="cboState" 				
07  :  																										onselectedindexchanged="cboState_SelectedIndexChanged">
08  :  	    			<asp:ListItem Text="Texas" Value="TX" />
09  :  	    			<asp:ListItem Text="Tennesee" Value="TN" />
10  :  	    			<asp:ListItem Text="Oklahoma" Value="OK" />
11  :  	        </asp:DropDownList>
12  :  	        <br />
13  :  	        City: 
14  :  			<asp:DropDownList runat="server" ID="cboCity">
15  :  	        </asp:DropDownList>
16  :  	    </ContentTemplate>
17  :  	</asp:UpdatePanel>


The first thing you might notice here is that the City list has no items. Given the nature of this list, it gets its items assigned in the code behind based on the state selection.

01  :  	public partial class DependantDropDownFilter : System.Web.UI.UserControl, IHostedUserControlFilter
02  :  	{
03  :  		protected void Page_Load(object sender, EventArgs e)	
04  :  		{
05  :  			///This is a fix needed by the Ajax Panel to be compatible with SharePoint
06  :  			ScriptManager.RegisterStartupScript(this, this.GetType(), "AjaxFix", 
07  :  				"<script type='text/javascript'>_spOriginalFormAction = document.forms[0].action; “ + 
08  :  				“_spSuppressFormOnSubmitWrapper=true;</script>"
09  :  				, false);
10  :  	
11  :  			if (!Page.IsPostBack)
12  :  			{
13  :  	        		///Force the state drop down to select the city upon load
14  :  	            cboState_SelectedIndexChanged(null, null);
15  :  			}
16  :  		}
17  :  	
18  :  		protected void cboState_SelectedIndexChanged(object sender, EventArgs e)
19  :  	    	{
20  :  	    		cboCity.Items.Clear();
21  :  	        	switch (cboState.SelectedValue)
22  :  	        	{
23  :  				case "TX":
24  :  	            		cboCity.Items.Add("Dallas");
25  :  					cboCity.Items.Add("Houston");
26  :  					cboCity.Items.Add("San Antonio");
27  :  					cboCity.Items.Add("Austin");
28  :  					break;
29  :  				case "TN":
30  :  	            		cboCity.Items.Add("Chatenooga");
31  :  					break;
32  :  				case "OK":
33  :  					cboCity.Items.Add("OKC");
34  :  					cboCity.Items.Add("Tulsa");
35  :  					break;
36  :  				default:
37  :  					break;
38  :  			}
39  :  		}
40  :  	
41  :  		#region IHostedUserControlFilter Members
42  :  		public List<string> Values
43  :  		{
44  :  			get
45  :  			{
46  :  	        		return new List<string>(new string[] { cboCity.SelectedValue });
47  :  			}
48  :  			set
49  :  	        	{
50  :  				if (value.Count > 0)
51  :  				{
52  :  					string City = value[0];
53  :  					switch (City)
54  :  					{
55  :  	        	        		case "OKC":
56  :  						case "Tulsa":
57  :  	        	             			cboState.Items.FindByValue("OK").Selected = true;
58  :  							cboState_SelectedIndexChanged(null, null);
59  :  							cboCity.Items.FindByValue(City).Selected = true;
60  :  							break;
61  :  						case "Chatenooga":
62  :  							cboState.Items.FindByValue("TN").Selected = true;
63  :  							cboState_SelectedIndexChanged(null, null);
64  :  							cboCity.Items.FindByValue(City).Selected = true;
65  :  							break;
66  :  						case "Dallas":
67  :  						case "Houston":
68  :  						case "Austin":
69  :  						case "San Antonio":
70  :  							cboState.Items.FindByValue("TX").Selected = true;
71  :  							cboState_SelectedIndexChanged(null, null);
72  :  							cboCity.Items.FindByValue(City).Selected = true;
73  :  							break;
74  :  						default:
75  :  							break;
76  :  					}
77  :  				}
78  :  			}
79  :  		}
80  :  		#endregion
81  :  	}


In this code behind you can see a few of the tricks that make this work. First off, this is an Ajaxified control. This means that selecting the state will change the city options, but not force a post back. This is important, because doing a post back against the server on a page that has a report viewer control on it, will cause that report to attempt to run against the parameters you have already selected. If you have not yet finished selecting your parameters, the nuisance factor on this could be pretty high, not to mention the possibility of additional stress on the reporting server.

In order to get the Ajax to work more than one, though, a piece of Javascript needs to be added. This merely suppresses some SharePoint form submission wrappers that get in the way of the partial page response.

The state selection logic is simple enough, given that these are hard coded values and the Values property getter simply returns the selected value of the City drop down control. The setter is important, though. The logic here is a little convoluted, and would likely be much simpler in the case of a SQL data source, but essentially what we are doing is determining the state that was selected based on the city. We select that state and force the selected index changed event handler to execute. We then set the city drop down to the selected value.

APPLYING THE FILTER VALUES

As I mentioned before, the filter values are applied to the report on post back to the server. Therefore, in order to get the values to the server, we will have to force a post back in some way. Microsoft’s filters do this by way of the Filter Actions web part. It sounds fancy, but essentially it is just a button with a check box for saving your personal defaults. To simulate this, we have to create our own Apply Filters button.
The basic concept is identical to the main HostedUserControlFilter web part. We want to host a user control for the UI purposes. We also want to raise an event from our UI layer when our apply filters button is clicked and expose a Boolean value indicating if the user chose to save defaults. Given this, we start with our UI interface.
01  :  	public interface IApplyFilters
02  :  	{
03  :  		bool SavePreferences { get; }
04  :  		event EventHandler<EventArgs> ApplyFilters;
05  :  	}


We can then create a user control for our UI as shown here
01  :  	<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ApplyFiltersTemplate.ascx.cs" 
02  :  	Inherits="CustomFilters.UserControls.ApplyFiltersTemplate, CustomFilters, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3302bed8990a619c" %>
03  :  	<asp:Button ID="btnApplyFilters" runat="server" Text="Apply Filters" 
04  :  	    onclick="btnApplyFilters_Click" /><br />
05  :  	<asp:CheckBox ID="cbSaveValues" runat="server" Text="Save Defaults"/>


With the following Code Behind
01  :  	public partial class ApplyFiltersTemplate : System.Web.UI.UserControl, IApplyFilters
02  :  	{
03  :  		protected void btnApplyFilters_Click(object sender, EventArgs e)
04  :  		{
05  :  		    ApplyFilters(this, null);
06  :  		}
07  :  	
08  :  		#region IApplyFilters Members
09  :  		public bool SavePreferences
10  :  		{
11  :  		    get { return cbSaveValues.Checked; }
12  :  		}
13  :  	
14  :  		public event EventHandler<EventArgs> ApplyFilters;
15  :  		#endregion
16  :  	}


As you can see here, we implemented the IApplyFilters interface allowing us to raise the ApplyFilters event when the button is clicked and exposing the check state of the Save Values check box.
This user control is then hosted in the ApplyFiltersButton web part. This web part functions, to some extent, just like the HostedUserControlFilter web part and the common functionality could likely be merged into a base class, but for the time being we’ll keep this simple. Let’s look at the web part code.
01  :  	[Guid("ea72d94a-eca1-47c0-9839-fc4c700fcfc4")]
02  :  	public class ApplyFiltersButton : Microsoft.SharePoint.WebPartPages.WebPart
03  :  	{
04  :  		private IApplyFilters localControl = null;
05  :  		private string virtualPath = string.Empty;
06  :  	
07  :  		public ApplyFiltersButton()
08  :  		{
09  :  		    this.ExportMode = WebPartExportMode.All;
10  :  		}
11  :  	
12  :  		protected override void CreateChildControls()
13  :  		{
14  :  		    base.CreateChildControls();
15  :  		    if (virtualPath != string.Empty && Page != null)
16  :  			try
17  :  			{
18  :  			    localControl = Page.LoadControl(virtualPath) as IApplyFilters;
19  :  			    if (localControl == null) throw new InvalidCastException("User Control must implement IApplyFilters");
20  :  	
21  :  			    localControl.ApplyFilters += new EventHandler<EventArgs>(localControl_ApplyFilters);
22  :  			    this.Controls.Add(localControl as Control);
23  :  			}
24  :  			catch
25  :  			{
26  :  				string error = string.Format("Unable to load control {0}.  Verify that control exists and uses the FQDN of its base class.", virtualPath);
27  :  			    this.Controls.Add(new LiteralControl(error));
28  :  			}
29  :  		}
30  :  	
31  :  		void localControl_ApplyFilters(object sender, EventArgs e)
32  :  		{
33  :  		    if (localControl.SavePreferences)
34  :  		    {
35  :  				foreach (Microsoft.SharePoint.WebPartPages.WebPart part in WebPartManager.WebParts)
36  :  				{
37  :  			    		HostedUserControlFilter filterPart = part as HostedUserControlFilter;
38  :  			    		if (filterPart != null)
39  :  			    		{
40  :  						filterPart.PersistPersonalizedChanges();
41  :  			    		}
42  :  				}
43  :  		    }
44  :  		}
45  :  	
46  :  		#region Settings
47  :  		[Personalizable(PersonalizationScope.Shared)]
48  :  		[WebBrowsable(true)]
49  :  		[System.ComponentModel.Category("Settings")]
50  :  		[WebDisplayName("Path to ASCX File")]
51  :  		[WebDescription("Give the complete (relative) URL to the user control")]
52  :  		public string VirtualPath
53  :  		{
54  :  		    get
55  :  		    {
56  :  			if (virtualPath == null)
57  :  			{
58  :  		   		 virtualPath = "";
59  :  			}
60  :  			return virtualPath;
61  :  		    }
62  :  		    set { virtualPath = value; }
63  :  		}
64  :  		#endregion
65  :  	}


As you can see, the functionality for the UI part of the web part is as we have already discussed. The difference is that we are now implementing a different interface and assigning an event handler to our ApplyFilters event. The ApplyFilters handler in this case doesn’t do anything regarding filters, but it does check to see if it should save the selected values as the current user’s default settings. This is the personalization part of the custom web filters and is the reason we have the setters on the Value properties in the User Controls
To do this, we have to first find all of the web parts we are going to personalize. This is done through the WebPartManager. In this case we are enumerating through each web part and checking if it is a HostedUserControlFilter. Once we know that is the case, we call the PersistPersonalizedChanges method on the web part. This is the method we left empty earlier in this article. Below is the additional code that needs to be added to the HostedUserControlFilter class to implement personalization.
01  :  	string[] personalizedDefaults = null;
02  :  	
03  :  	[WebBrowsable(false),
04  :  	WebPartStorage(Storage.Personal),
05  :  	Personalizable(PersonalizationScope.User),
06  :  	DefaultValue((string)null)]
07  :  	public string[] PersonalizedDefaults
08  :  	{
09  :  	    get
10  :  	    {
11  :  			return personalizedDefaults;
12  :  	    }
13  :  	    set
14  :  	    {
15  :  			personalizedDefaults = value;
16  :  	    }
17  :  	}
18  :  	
19  :  	internal void PersistPersonalizedChanges()
20  :  	{
21  :  	    if (ParameterValues != null)
22  :  	    {
23  :  			List<string> pValues = new List<string>();
24  :  			pValues.AddRange(ParameterValues);
25  :  			personalizedDefaults = pValues.ToArray();
26  :  	
27  :  			using (SPLimitedWebPartManager scopedWPM = SPContext.Current.Web.GetLimitedWebPartManager(HttpContext.Current.Request.Url.AbsoluteUri, PersonalizationScope.User))
28  :  			{
29  :  		    		///Apparently, when you pull the Web Part from the  SPLimitedWebPartManager
30  :  		    		///it is not the actual web part, but a deserialized version pulled from storage.
31  :  		    		///Thus, you need to set the things you would like to have saved on a user level at this 
32  :  		    		///point.
33  :  	
34  :  		    		HostedUserControlFilter scopedPart = scopedWPM.WebParts[this.ID] as HostedUserControlFilter;
35  :  		    		scopedPart.PersonalizedDefaults = personalizedDefaults;
36  :  		    		scopedWPM.SaveChanges(scopedPart);
37  :  			}
38  :  	    }
39  :  	}
40  :  	 
41  :  	public HostedUserControlFilter()
42  :  	{
43  :  	    this.ExportMode = WebPartExportMode.All;
44  :  	    this.Load += new EventHandler(HostedUserControlFilter_Load);
45  :  	}
46  :  	
47  :  	void HostedUserControlFilter_Load(object sender, EventArgs e)
48  :  	{
49  :  	    if (!Page.IsPostBack)
50  :  	    {
51  :  			#region Load Personalizations
52  :  			if (personalizedDefaults != null)
53  :  			{
54  :  			    List<String> pv = new List<string>(personalizedDefaults);
55  :  			    System.Collections.ObjectModel.ReadOnlyCollection<string> parmValues = 
																										new  System.Collections.ObjectModel.ReadOnlyCollection<string>(pv);
56  :  			    ParameterValues = parmValues;
57  :  			}
58  :  			#endregion
59  :  	    }
60  :  	}


There are three basic pieces to the personalization system. First, we have to determine what to personalize. For this situation we are just going to store the selected values that would be output by the filter. This is the PersonalizedDefaults property of the web part. Next we have to actually store that information. This is accomplished by the PersistPersonalizedChanges method.
To persist the personalized changes, we first have to acquire a specific web part manager that is limited to the User scope. Once we have that web part manager, we then retrieve a copy of our current web part by passing in the web part’s ID. This copy is a deserialized instance of the web part with no Page context. Now we set the PersonalizedDefaults property of our web part copy and asked the Scoped Web Part Manager to save the web part. Mission Accomplished.
The final part is reloading the defaults. This is pretty simple, really. The web part manager will load them on next page load automatically since they are now part of the user’s web part context. The PersonalizedDefaults property is automatically set via deserialization and all we need to do is configure our UI. This is done in the Load event handler on page first load (!IsPostBack).

CONCLUSION

And there you have it. Custom Report Viewer filters with simple to change UIs. Obviously, these classes need to be wrapped into a SharePoint solution and deployed, but that is beyond the scope of this article. I highly recommend the WSPBuilder and the VS WSPBuilder Addon for building WSP functionality. It is available on CodePlex.

Last edited Jul 22, 2009 at 9:56 PM by Rogerstigers, version 6