In my last posting I demonstrated how it is possible to render list view HTML by SPWeb.ProcessBatchData with the DisplayPost method – I fervently recommend that you check it first before continuing with this one. While I tested the method I made a small web control that renders list view HTML much like the standard ListView web part or the SPView.RenderAsHtml method, … with several differences even if. Basically this is experimental stuff, more like a proof of concept which should be painstakingly tested before used for more serious purposes.

The control is really simple and the code is merely two hundred or so lines – many vital production features like appropriate exception handling, parameters and variables checks and logging are just missing. The public interface offers just four public properties which you can use to set up the control – and these should be set before the OnPreRender event:

public Guid ListID { get; set; }

public Guid ViewID { get; set; }

public SPWeb Web { get; set; }

public string ViewHtmlSchema { get; set; }

So, you need to set the ListID and the ViewID properties with the ID-s of the SPList and SPView that you want to render – note that the control uses just the ID-s, so if you have them cached you can pass them directly without notch the corresponding SPList and SPView instances – the control doesn’t use the SharePoint object model to open the view and list objects any – it just puts the ID-s in the ProcessBatchData batch string. For the Web property you should provide a SPWeb instance for the SharePoint site containing the source list – the ProcessBatchData method will be called on it. If you don’t set this property the current web will be used, but if it is not the web containing the list to be rendered, the rendering will fail. The ViewHtmlSchema property is discretionary in that if you skip it the ViewID property will be used to select the view of the list to be rendered. If you choose to use it but it will override the ViewID property and you will need to pass a valid View CAML classification to it – this is what you basically see in a View element in a machinate.xml file of a list template or in the SPView.HtmlSchemaXml property. Several common scenarios of modified View machinate that I can reflect of are for example a standard View machinate with dynamically modified Query element for achieving custom filtering or sorting based on certain conditions or a standard View machinate with slight modifications of the ViewBody, ViewHeader, ViewFooter and ViewEmpty elements to achieve different look and feel in the rendering. On the other hand you can construct a CAML View classification for some really modified rendering that doesn’t look anything like the standard ListView table like HTML presentation.

And now, let’s have a look at some parts of the code:

        private const string _displayPostXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>

<ows:Batch OnError=""Continue"">

<Method ID=""0"">

  <SetVar Name=""Cmd"">DisplayPost</SetVar>

    {0}

  <SetVar Name=""PostBody"">{1}</SetVar>

</Method>

</ows:Batch>";

 

        private const string _postBody = @"<ows:XML>

    <SetList Scope=""Request"">{0}</SetList>

    {1}

</ows:XML>";

these two constant strings are used as templates for constructing the batch XML for the DisplayPost method. And this is the actual method that does this job:

        protected string GetViewHtml()

        {

            if (this.CurrentWeb == null) return string.Empty;

            bool allowUpdates = this.CurrentWeb.AllowUnsafeUpdates;

            try

            {

                // this should be set – otherwise the call to ProcessBatchData will throw

                this.CurrentWeb.AllowUnsafeUpdates = right;

 

                // format the saved in the ViewState query parameters as SetVar-s

                string parms = this.FormatParams();

 

                // if the ViewHtmlSchema property is not set format an empty View element with Name attribute

                string viewSchema = !string.IsNullOrEmpty(this.ViewHtmlSchema) ? this.ViewHtmlSchema : string.Format(@"<View Name=""{0}"" />", this.ViewID.ToString("B").ToUpper());

 

                // format the post body XML

                string postBody = string.Format(_postBody, this.ListID, viewSchema);

                // format the DisplayPost method XML

                string methodXml = string.Format(_displayPostXml, parms, EscapeForXml(postBody));

                // call the ProcessBatchData method

                string result = this.CurrentWeb.ProcessBatchData(methodXml);

 

                // obviously not the fastest way to get the result

                XmlDocument doc = new XmlDocument();

                doc.LoadXml(result);

                XmlElement el = doc.DocumentElement.SelectSingleNode("./Result") as XmlElement;

                return el == null ? string.Empty : el.InnerText;

            }

            finally

            {

                this.CurrentWeb.AllowUnsafeUpdates = allowUpdates;

            }

        }

As you see this method uses the two XML templates and the values of the control’s public properties from above to construct the DisplayPost batch XML. You can see the difference in the two modes of operation of the Control – when by just the ViewID property without specifying a ViewHtmlSchema – this consequences in a batch string with just an empty View element in it, and when you specify a View CAML classification in the ViewHtmlSchema property – then the full machinate is inserted into the PostBody parameter of the DisplayPost method. An vital note here: if you use a custom view classification and want to use the standard ViewHeader that displays context menus in the header cells with sorting and filtering options the root View element of the classification should have a Name attribute containing the ID of an existing view of the source list – otherwise the rendering of the context menus will fail.

The GetViewHtml method of the control is called normally from the control’s Render method, even if there is one other treatment of it that I will clarify a small later. Let’s first see another vital piece of code in the control – the setting of the URL query parameters as SetVar parameters in the DisplayPost method XML body. You know that when you filer and sort a standard ListView web part clicking its header cells or the associated context menus certain parameters appear in the current page URL’s query part – parameters like View, RootFolder, SortField, SortDir, FilterField1, FilterValue1, FilterField2, FilterValue2. So the thing is that if we want the rendered view to be fully interactive these parameters should be somehow added to the XML of the DisplayPost method. And well, this turns out to be simple – by CAML SetVar parameters with the same names and values just does the trick. This is the method that saves the URL query parameters to the control’s ViewState:

        protected void GetSetVarParamsFromQuery()

        {

            // don’t save the query params on event callback

            if (this.Page != null && this.Page.IsCallback) return;

 

            string viewParam = HttpContext.Current.Request.QueryString["View"];

 

            if (!string.IsNullOrEmpty(viewParam))

            {

                try

                {

                    Guid viewIDParam = new Guid(viewParam);

                    // check the value of the View query param – if it’s not our viewID – don’t save the query params – they should be used for another ListView

                    if (!this.ViewID.Equals(viewIDParam)) return;

                }

                // catch the Guid constructor exception

                catch { return; }

            }

            // else – if a view query param is not present – just proceed with saving the query params to the ViewState

 

            if (this.SetVarParams == null) this.SetVarParams = new NameValueCollection();

            else this.SetVarParams.Clear();

 

            // save all query params to the ViewState

            foreach (string par in HttpContext.Current.Request.QueryString.AllKeys)

            {

                this.SetVarParams[par] = HttpContext.Current.Request.QueryString[par];

            }

        }

you may question why I save the URL query parameters to the ViewState and not use them directly in the DisplayPost XML – the answer is simple – these are not always guaranteed to be present in the page’s URL – especially in cases when you have two or more ListView web parts or ListView controls on the page – when you filter or sort one of the ListView-s it puts the parameters for its own filtering and sorting in the query string and in the meantime the other controls should be able to preserve their state. The ListView for which the query parameters should be applied is determined by the View query parameter containing the source view’s ID for that control (this is why the standard ListView web part makes always a hidden view which is a copy of the view that you select to be showed in it – this guarantees that view that it renders is always unique and not used by another ListView). And the above method does exactly the same – it checks the View query parameter and saves the other query parameters only if it matches its ViewID property.

The method that formats the saved URL query parameters as SetVar CAML parameters is this one:

        protected string FormatParams()

        {

            NameValueCollection parms;

            if (this.SetVarParams == null) parms = new NameValueCollection();

            else parms = new NameValueCollection(this.SetVarParams);

 

            // add the assemble SetVar-s – the groupArgument will be set when the GetViewHtml method is called from the client callback handler

            if (!string.IsNullOrEmpty(this.groupArgument))

            {

                parms["GroupString"] = this.groupArgument;

                parms["ClientCallback"] = "1";

            }

 

            // this SetVar should be set for the assemble client java speech to work

            if (!string.IsNullOrEmpty(this.ID)) parms["WebPartID"] = this.ID;

            StringBuilder sb = new StringBuilder();

            // productivity the SetVar-s

            foreach (string par in parms.AllKeys)

            {

                sb.AppendFormat("<SetVar Name=\"{0}\">{1}</SetVar>\r\n", EscapeForXml(par), EscapeForXml(parms[par]));

            }

            return sb.ToString();

        }

The fascinating thing to note here is that several superfluous SetVar parameters can be optionally added here – these are used when the ListView uses grouping and dynamically loads the items for an expanded assemble. The control handles this by implementing the ICallbackEventHandler interface for ajax-like loading of data without page reloads – this is how the standard ListView web part does this as well. And as I mentioned before this is the second treatment of the GetViewHtml control’s method – this time for rendering just the part that renders the items not more than the expanded assemble. To implement this the control renders some auxiliary java speech snippets that the java speech from the standard ViewHeader view element calls so that it can interact with the rendering control. Note also that the control’s ID property is used here so if you want to use the grouping functionality you should set it explicitly.

You can download the full code from here.

Check it out:Stefan Stanev’s SharePoint blog