The title may infer that this article is about consolidating list data across multiple sites - sorry, it is actually about looking up list data from a single list using the OOTB XsltListViewWebPart in a different site
"Value does not fall within the expected range" - one of the more common errors received when developing with the .NET framework - but one that can usually be resolved very simply - unless of course it is being thrown by the SharePoint API during, what could be considered, a standard user process!
Background
SharePoint Designer 2010 gives user's the ability to save the web part used to display views of a list to it's own .webpart file, enabling the view to be placed on any other pages in the site. This functionality suggests that the web part can then be placed on any page in any site within the site collection.
To save an XsltListViewWebPart instance of a list view, open the nameofview.aspx page in SharePoint Designer 2010, ensure Design view is visible, select the existing web part and use the Save Web Part ribbon commands (found in the Web Part tab).

When saving the view web part to the site gallery the following dialog will be displayed, select "Yes" to ensure the list reference is NOT relative to the site in which the web part is placed.

So, take the following scenario:
- A web application consists of a root site collection and multiple sub-sites.
- The root site collection contains a list containing items that pages in the sub-site will be displaying
Error
When editing a page in a sub-site, the Insert web part ribbon panel will not typically show lists or libraries from the root site collection, but having saved an instance of a view web part it will be made available (usually in the miscellaneous group, unless set to another group by the user).
Unfortunately, errors occur when adding the web part to a site other than that where the original list instance exists. After adding the web part onto a page the "Value does not fall within the expected range" exception will occur during certain usage scenarios e.g. paging, filtering, item deletion, addition of other web parts.

What makes the error occur
Taking a quick look into the .webpart file that is saved to the Site Gallery shows that all required values are being serialised into the web part, e.g. WebId, ListId and XmlDefinition. (To see this for yourself just export the web part to a file using SharePoint Designer and open it). So what could possibly be wrong? Looking into the exception call stack you will notice a call to Microsoft.SharePoint.WebControls.NewMenu.AddMenuItems(), this is called by the embedded ToolBar, the one which displays the Add new item link at the bottom of the list view, and this is the source of all the errors!

This toolbar is created regardless of whether it is actually displayed or not - so simply setting the ToolbarType to None in the .webpart file will not eliminate any of the potential issues.
The problem with the AddMenuItems() method is that internally defaults to using the current context SPWeb in order to locate the RootFolder of the List being queried - now in our case the current context is different to the actual SPWeb where the List resides, hence the Value out of range exception. This does not occur when creating the underlying datasource as this is set to correctly pull in the correct view from the correct list.

The important line in the above source code is line 119. this.Web references the current web where the page housing the web part is located - unfortunately this then causes the call to GetFolder to fail, as the base.RenderContext.RootFolderUrl is referencing the list where the data is.
We know the problem, so how to fix it?
This has certainly been a headache for me recently, and one which I have spent a lot of time trying to resolve. Unfortunately this article is not going to resolve all the issues that occur when trying to view list data from a parent site using a saved instance of the XsltListViewWebPart but it does go some way to enable the majority of use-cases to succeed - that is until Microsoft resolve the issue themselves.
First, we understand where the error occurs - at least we have the call stack telling us where - but how to go about resolving this? Taking a look into the XsltListViewWebPart class you will notice a property named ToolbarControl - this holds a reference to the toolbar which inevitably renders the add new links. Luckily this property exposes the RenderContext object, which is used to populate the Web property of the NewMenu control. So, if we can set the RenderContext's Web to the correct SPWeb then we will no longer get the exception…
Resolution (aka workaround, aka potentially-unsupported-fix)
Below is a class file for a web part, that, when placed on a page will fix-up the RenderContext of the toolbar control. It will find all XsltListViewWebPart's and determine any where the current SPWeb is different to the one necessary to render the list view correctly...and fix it!
1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel;
4: using System.Linq;
5: using System.Reflection;
6: using System.Web;
7: using Microsoft.SharePoint;
8: using Microsoft.SharePoint.WebControls;
9: using Microsoft.SharePoint.WebPartPages;
10: using WebPart = System.Web.UI.WebControls.WebParts.WebPart;
11:
12: namespace XlstListModifier.XsltListViewModifier
13: {
14: /// <summary>
15: /// An error exists when an XsltListViewWebPart is retrieving its data from a List on a different SPWeb that that of the
16: /// SPContext.Current.Web. It occurs when the Microsoft.SharePoint.WebControls.NewMenu is rendered (The NewMenu is rendered
17: /// due to the default rendering of the embedded ToolbarControl in the XsltListViewWebPart. During the rendering of the NewMenu
18: /// the list of SPContentType's that are used by the SPList being rendered is retrieved:
19: ///
20: /// [Microsoft.SharePoint.WebControls.NewMenu.AddMenuItems()]
21: /// IList<![CDATA[<SPContentType>]]> list;
22: /// if (base.RenderContext.RootFolderUrl != null)
23: /// {
24: /// list = new List<![CDATA[<SPContentType>]]>(this.Web.GetFolder(base.RenderContext.RootFolderUrl).ContentTypeOrder);
25: /// }
26: /// else
27: /// {
28: /// list = new List<![CDATA[<SPContentType>]]>(this.List.RootFolder.ContentTypeOrder);
29: /// }
30: ///
31: /// The base.RenderContext is not null and therefore the first statement gets executed. this.Web is referencing the
32: /// RenderContext.Web object (which by default uses the SPContext.Current.SPWeb (unless otherwise specified when creating)
33: /// and therefore fails when the RootFolder object is for an SPList in a different SPWeb.
34: ///
35: /// The code below sets the RenderContext of the ViewToolBar to the correct SPWeb, as well as setting the SPList and SPView
36: /// GUID's. The property exposing the toolbar object is unfortunately an internal property though, hence the use of
37: /// reflection to access the property and set it correctly.
38: /// </summary>
39: [ToolboxItemAttribute(false)]
40: public class XsltListViewModifier : WebPart
41: {
42: private bool _bFixed;
43: private readonly Dictionary<Guid, SPWeb> _oWebs = new Dictionary<Guid, SPWeb>();
44:
45: protected override void OnInit(EventArgs e)
46: {
47: base.OnInit(e);
48: Title = "XSLT List View Modifier WebPart";
49: }
50:
51: protected override void OnLoad(EventArgs e)
52: {
53: base.OnLoad(e);
54:
55: if (!_bFixed && Page.IsPostBack)
56: {
57: ApplyFix();
58: _bFixed = true;
59: }
60: }
61:
62: protected override void OnPreRender(EventArgs e)
63: {
64: base.OnPreRender(e);
65:
66: ApplyFix();
67: _bFixed = true;
68: }
69:
70: private void ApplyFix()
71: {
72: IEnumerable<XsltListViewWebPart> oParts = GetXsltParts();
73: foreach (XsltListViewWebPart oPart in oParts.Where(oPart => oPart.WebId != SPContext.Current.Web.ID))
74: SetToolbarContext(oPart);
75: }
76:
77: /// <summary>
78: /// Get an array of XsltListViewWebPart's that exist on the current WebPartPage
79: /// </summary>
80: /// <returns>An array of XstlListViewWebPart's</returns>
81: private IEnumerable<XsltListViewWebPart> GetXsltParts()
82: {
83: return WebPartManager.WebParts.OfType<XsltListViewWebPart>().ToArray();
84: }
85:
86: private void SetToolbarContext(XsltListViewWebPart part)
87: {
88: try
89: {
90: //Get hold of the toolbar that renders the new menu items
91: ViewToolBar oTbar = GetPrivatePropertyValue<ViewToolBar>(part, "ToolbarControl");
92: if (oTbar == null) return;
93:
94: //We keep a collection of SPWeb objects in case there is more than on XsltListViewWebPart on
95: //the page looking at lists in more than one site (SPWeb). This way we don't instanciate more
96: //than one SPWeb for each SPWeb needed
97: SPWeb oWb;
98: if (_oWebs.ContainsKey(part.WebId))
99: {
100: oWb = _oWebs[part.WebId];
101: }
102: else
103: {
104: oWb = SPContext.Current.Site.AllWebs[part.WebId];
105: _oWebs.Add(part.WebId, oWb);
106: }
107:
108: //Create the SPContext object that references the correct SPList, SPView and most importantly..SPWeb
109: SPContext oCtx = SPContext.GetContext(HttpContext.Current, new Guid(part.ViewGuid), part.ListId, oWb);
110: //Set the RenderContext of the ToolbarControl
111: oTbar.RenderContext = oCtx;
112: }
113: catch (Exception)
114: { }
115: }
116:
117: public override void Dispose()
118: {
119: base.Dispose();
120:
121: //Ensure that any webs are disposed of incase the GC doesn't pick it up during object disposal
122: foreach (Guid oId in _oWebs.Keys) _oWebs[oId].Dispose();
123: }
124:
125: public static T GetPrivatePropertyValue<T>(object o, string name)
126: {
127: if (o == null) throw new ArgumentNullException("o");
128: PropertyInfo oInfo = o.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
129: if (oInfo == null) throw new ArgumentOutOfRangeException("name", string.Format("Property {0} was not found in Type {1}", name, o.GetType().FullName));
130: return (T)oInfo.GetValue(o, null);
131: }
132: }
133: }
OK...so this resolves it, it really does...but notice the use of reflection? Yes, the ToolbarControl property is an internal property, we are not provided access to this using standard API methods, but it is integral to the problem and we MUST solve the RenderContext issue in order to proceed.
Thoughts
Some may consider that viewing list view information in this manner is not actually supposed to occur, others will totally disagree (me included). The facts are simple:
- SharePoint Designer 2010 exposes the ability to save the XsltListViewWebPart instances
- SharePoint 2010 itself then enables users to add the web part to any page in any site
- No other OOTB control or web part exposes the exact same functionality as the XsltListViewWebPart (seriously, try matching it 100% using something else - without using more lines of code than what I posted above!!)
- There are certainly cases where viewing information from a list in another site is a business requirement - I wouldn't have spent so long on the issue had it not been a "must have"
So, the trade-off is simple, for the sake of ensuring that any future updates and hot-fixes get tested thoroughly to ensure we don't lose anything by using an internal property, we get an XsltListViewWebPart that supports all OOTB features without spending hours and hours trying to rewrite SharePoint! The errors covered in this article have been reported to Microsoft, and I am currently waiting for any feedback. At least using my approach for fixing the issue, if an official fix is ever released the above web part can just be removed from any pages, instead of painfully replacing a custom carbon copy that will rarely tick all the boxes.
Download
Download the web part code (as posted above)
Comments