I had a lot of good feedbacks on that SharePoint filtering articles series, the guys that wrote me spotted on a nasty bug on how I was building the chain of filters in CAML.
In my code I did something like: <or><eq>...</eq><eq>...</eq><eq>...</eq></or> which is WRONG! Due to the lack of a good testing (I worked with only two values I didn’t get it at start).
The correct chain should be: <or><or><eq>...</eq><eq>...</eq></or><eq>...</eq></or>.
Today I found 1 hour to fix the problem which was present in two pieces of the code: once when building the single filters and once when concatenating the multiple filters to obtain the final query.
Here’s the code that contains the bug fixes. As a note to all my readers: again I did not had time to test it deeply (I checked the algorithm to generate the query, but I didn’t tested it against a SharePoint database due to the lack of time), so check it before using it in a production environment.
Any further suggestion, modification and fixes are welcome :D
In my code I did something like: <or><eq>...</eq><eq>...</eq><eq>...</eq></or> which is WRONG! Due to the lack of a good testing (I worked with only two values I didn’t get it at start).
The correct chain should be: <or><or><eq>...</eq><eq>...</eq></or><eq>...</eq></or>.
Today I found 1 hour to fix the problem which was present in two pieces of the code: once when building the single filters and once when concatenating the multiple filters to obtain the final query.
Here’s the code that contains the bug fixes. As a note to all my readers: again I did not had time to test it deeply (I checked the algorithm to generate the query, but I didn’t tested it against a SharePoint database due to the lack of time), so check it before using it in a production environment.
[Guid("b7ea3f7d-b260-4ce8-9fd0-3af5aee8e0d6")]
public class CustomListViewWebPart : System.Web.UI.WebControls.WebParts.WebPart
{
#region Properties
private readonly ILogger _logger = LoggerFactory.GetLogger();
/// <summary>
/// The List we are displaying
/// </summary>
[Personalizable(true),
WebBrowsable,
WebPartStorage(Storage.Shared),
SPWebCategoryName("Settings"),
WebDisplayName("Source List Name"),
WebDescription("Pass the name of the List to show")]
public string SourceList { get; set; }
/// <summary>
/// The Default View of the list
/// </summary>
[Personalizable(true),
WebBrowsable,
WebPartStorage(Storage.Shared),
SPWebCategoryName("Settings"),
WebDisplayName("View"),
WebDescription("Pass the name of the View that you want to apply to the List")]
public string ViewOfSourceList { get; set; }
/// <summary>
/// a CAML query to filter the object
/// </summary>
/// <remarks>
/// in a later revision we will use one or more filter providers to set this
/// </remarks>
[Personalizable(true),
WebBrowsable,
WebPartStorage(Storage.Shared),
SPWebCategoryName("Settings"),
WebDisplayName("Query"),
WebDescription("Pass the Filter Query, if you wire up some Filter WebParts they have priority over the query and this will be ignored")]
public string FilterQuery { get; set; }
private readonly List<IFilterValues> _filterProviders = new List<IFilterValues>();
private List<IFilterValues> FilterProviders
{
get { return _filterProviders; }
}
#endregion
public CustomListViewWebPart()
{
ExportMode = WebPartExportMode.All;
}
protected override void CreateChildControls()
{
base.CreateChildControls();
SPWeb web = SPContext.Current.Web;
{
try
{
SPList list = web.Lists[SourceList];
// create the toolbar, actually we cannot hide it, we'll need to extend the webpart and those options
ViewToolBar toolbar = new ViewToolBar();
SPContext context = SPContext.GetContext(Context, list.Views[ViewOfSourceList].ID, list.ID, SPContext.Current.Web);
toolbar.RenderContext = context;
Controls.Add(toolbar);
// get a reference to the view we want to use (or use the default view if nothing is specified)
SPView webPartView;
if (!string.IsNullOrEmpty(ViewOfSourceList))
webPartView = web.Lists[SourceList].Views[ViewOfSourceList];
else
webPartView = web.Lists[SourceList].DefaultView;
// create a new view based on the original one and attach the filter query to it
// in this way we do not need to modify/update the original element and
// even a user without updating permissions can use this webpart
XmlDocument domDoc = new XmlDocument();
domDoc.LoadXml(webPartView.SchemaXml);
SPView view = new SPView(list, domDoc);
_logger.AppendLogFormat("View ID: {0}", view.ID);
// build the filter
ApplyFilters(list, view);
// render the view
Literal lbl = new Literal();
lbl.Text = view.RenderAsHtml();
Controls.Add(lbl);
// add the logging messages if there are any
string lg = _logger.ToString();
if (!string.IsNullOrEmpty(lg))
{
Literal logLbl = new Literal();
logLbl.Text = "<br/>" + lg;
Controls.Add(logLbl);
}
}
catch (Exception ex)
{
// todo: have a better way to report errors!
Label lbl = new Label();
lbl.Text = _logger.ToString() + "<br />";
lbl.Text += "Error occured: ";
lbl.Text += ex.Message + "<br />" + ex.StackTrace;
Controls.Add(lbl);
}
}
}
/// <summary>
/// how to concatenate different filters
/// </summary>
const string FiltersConcatenation = "And";
/// <summary>
/// how to concatenate multiple values in the same filter
/// </summary>
const string FiltersMultipleValuesConcatenation = "Or";
private void ApplyFilters(SPList list, SPView view)
{
_logger.AppendLogFormat("Filters numbers: {0}", FilterProviders.Count);
if (FilterProviders.Count == 0)
view.Query = FilterQuery;
else
{
if (FilterProviders.Count > 0)
{
// build the filter according to the passed data
// holds each internal filter
List<string> filters = BuildFiltersList(list);
// assign the list of filters, concatenate them if more than one
if (filters.Count > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("<Where>");
if (filters.Count == 1)
sb.Append(filters[0]);
else
{
// we have more than 1 filter and we need to concat
for (int i = 0; i < filters.Count; i++)
{
if (i != 1)
sb.Insert(0, String.Format("<{0}>", FiltersConcatenation));
sb.Append(filters[i]);
if (i > 0)
sb.AppendFormat("</{0}>", FiltersConcatenation);
}
}
sb.Append("</Where>");
string query = sb.ToString();
view.Query = query;
_logger.AppendLog("query: {0}" + query);
}
_logger.AppendLog("query: -");
}
}
}
/// <summary>
/// build the filter list to apply to the view
/// </summary>
/// <param name="list"></param>
/// <returns></returns>
private List<string> BuildFiltersList(SPList list)
{
List<string> filters = new List<string>();
ReadOnlyCollection<string> paramValues;
foreach (IFilterValues f in FilterProviders)
{
paramValues = f.ParameterValues;
_logger.AppendLogFormat("Filter: {0}", f.ParameterName);
_logger.AppendLogFormat("Filter Params: {0}", (paramValues != null));
if ((paramValues != null) && (paramValues.Count > 0))
{
StringBuilder innerSb = new StringBuilder();
_logger.AppendLogFormat("Found filter: {0} values n: {1}", f.ParameterName, paramValues.Count);
// check for single parameter value
if (paramValues.Count == 1)
innerSb.AppendFormat("<Eq><FieldRef Name='{0}' /><Value Type='{1}'>{2}</Value></Eq>",
list.Fields[f.ParameterName].InternalName,
list.Fields[f.ParameterName].TypeAsString,
paramValues[0]);
else
{
// multiple values we need to concat in the right way for a CAML query
for (int i = 0; i < paramValues.Count; i++)
{
if (i != 1)
innerSb.Insert(0, String.Format("<{0}>", FiltersMultipleValuesConcatenation));
innerSb.AppendFormat("<Eq><FieldRef Name='{0}' /><Value Type='{1}'>{2}</Value></Eq>",
list.Fields[f.ParameterName].InternalName,
list.Fields[f.ParameterName].TypeAsString,
paramValues[i]);
if (i > 0)
innerSb.AppendFormat("</{0}>", FiltersMultipleValuesConcatenation);
}
}
filters.Add(innerSb.ToString());
}
}
return filters;
}
protected override void Render(HtmlTextWriter writer)
{
EnsureChildControls();
base.Render(writer);
}
[ConnectionConsumer("Filters", "FiltersForConsumer", AllowsMultipleConnections = true)]
public void SetFilter(IFilterValues filterValues)
{
if (filterValues != null)
{
// EnsureChildControls();
_logger.AppendLog("Assigning filters");
_logger.AppendLogFormat("Assigning filter: {0}", filterValues.ParameterName);
_logger.AppendLogFormat("Assigning filter: {0}", (filterValues.ParameterValues != null));
filterValues.SetConsumerParameters(GetParameters());
FilterProviders.Add(filterValues);
_logger.AppendLog("Filters Assigned");
}
}
/// <summary>
/// Build a parameter List to allow the filtering of the List on the values provided
/// by a series of filters.
/// </summary>
/// <returns></returns>
private ReadOnlyCollection<ConsumerParameter> GetParameters()
{
List<ConsumerParameter> parameters = new List<ConsumerParameter>();
// we get all the fields of the list we are displaying
SPList list = SPContext.Current.Web.Lists[SourceList];
// we build a parameter for any field of the list (similar to the standard ListViewWebPart)
foreach (SPField item in list.Fields)
parameters.Add(new ConsumerParameter(item.Title,
ConsumerParameterCapabilities.SupportsMultipleValues |
ConsumerParameterCapabilities.SupportsAllValue |
ConsumerParameterCapabilities.SupportsEmptyValue |
ConsumerParameterCapabilities.SupportsSingleValue));
return new ReadOnlyCollection<ConsumerParameter>(parameters);
}
}
Related Content
- WSS / SharePoint : filter Users with the PeopleEditor control (26/08/2015)
- WSS/SharePoint: create a Current User Filter Web Part (09/07/2009)
- WSS / SharePoint: adding filtering capabilities to the CustomListViewWebPart (Current User Filter and others) (09/11/2009)
- More related document (1)