Thursday, May 6, 2010

How to display all versions in a SharePoint list view page – part 2

Several months ago I wrote a small posting describing how you can trick the standard list view web part to display all versions of the list items in a SharePoint list – link. One problem though with this was that even if the list view columns display the values of the relevant item versions the “Name” column in document libraries links always to the current version of document (the same holds for the “Title” column in non-library lists and also for the commands in the context menu of the item). In one of the comments of the original posting there was a request to me to demonstrate a solution or work-around for that which is what I am going to elaborate on in this posting. The solution is applicable for document libraries only and uses a custom computed field which renders as a link to the document which when clicked opens the correct version of the selected document. The link actually calls a small custom application page (that should be deployed to the “12/TEMPLATE/LAYOUTS” folder of the SharePoint installation) which with several lines of code using the SharePoint object model redirects either to the current document version or to one of its previous ones (with the _VTI_HISTORY URL pattern).

And the implementation itself, the schema XML of the computed field:

<Field ID="{ccccdddd-3710-4374-b433-61cb4a686c12}"

       ReadOnly="TRUE"

       Type="Computed"

       Name="VersionLinkFilename"

       DisplayName="Version link"

       DisplayNameSrcField="FileLeafRef"

       Filterable="FALSE"

       AuthoringInfo="(linked to version)"

       SourceID="http://schemas.microsoft.com/sharepoint/v3"

       StaticName="VersionLinkFilename" FromBaseType="TRUE">

  <FieldRefs>

    <FieldRef Name="FileLeafRef" />

    <FieldRef Name="FileRef" />

    <FieldRef Name="_UIVersion" />

  </FieldRefs>

  <DisplayPattern>

    <HTML><![CDATA[<a href=']]></HTML>

    <HttpVDir CurrentWeb="TRUE" ></HttpVDir><HTML><![CDATA[/_layouts/versionredir.aspx?FileRef=%2f]]></HTML><LookupColumn Name="FileRef" URLEncode="TRUE" /><HTML><![CDATA[&Version=]]></HTML><Column Name="_UIVersion" />

    <HTML><![CDATA['>]]></HTML>

    <LookupColumn Name="FileLeafRef" />

    <HTML><![CDATA[</a>]]></HTML>

  </DisplayPattern>

</Field>

The easiest way to add a computed filed to your document library is using code (or a custom tool), basically you can use the SPFieldCollection.AddFieldAsXml method (calling it on the Fields property of your SPList instance) providing the schema of the field in the first parameter.

And the code of the custom application page:

<%@ Page Language="C#" Debug="true" %>

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%@ Import Namespace="Microsoft.SharePoint" %>

<script runat="server">

    protected override void OnLoad(EventArgs e)

    {

        base.OnLoad(e);

 

        string fileRef = this.Request.QueryString["FileRef"];

        int version = int.Parse(this.Request.QueryString["Version"]);

 

        SPWeb web = SPContext.Current.Web;

        SPFile file = web.GetFile(fileRef);

        SPListItem item = file.Item;

 

        string redirUrl = null;

        if ((int)item["_UIVersion"] == version) redirUrl = web.Url.TrimEnd('/') + "/" + file.Url;

        else

        {

            foreach (SPListItemVersion v in item.Versions)

            {

                if ((int)v["_UIVersion"] == version)

                {

                    redirUrl = web.Url.TrimEnd('/') + "/_vti_history/" + version + "/" + file.Url;

                    break;

                }

            }

        }

        if (redirUrl != null)

        {

            Response.Redirect(redirUrl);

        }

    }

</script>

The page should be saved as “VersionRedir.aspx” in the LAYOUTS folder of your SharePoint hive.

17 comments:

  1. I have created the VersionRedir.aspx page and I am trying to test it by manually creating a URL to open a previous version of a document. The sharepoint address I am accessing is http://intranet/documents and the document in question is called test. Would the calculated URL be http://intranet/documents/_layouts/versionredir.aspx?FileRef=test.docx
    &Version=1

    ReplyDelete
  2. the URL should be http://intranet/_layouts/versionredir.aspx?FileRef=/documents/test.docx
    &Version=1 - you have the _layouts part immediately after the site URL part and the FileRef parameter should contain the full server relative path of the document.
    about the Version parameter - it should correspond to the _UIVersion system field, not the _UIVersionString one. You can have a value of 1 in the _UIVersion column only when you have minor versions enabled, the rule is that for major versions you have a value of the _UIVersion field divisible by 512 - e.g. 512, 1024, etc, so maybe it is a good idea to try with one of these too.

    ReplyDelete
  3. How can I use theese special parameters in view by using filters?

    ReplyDelete
  4. Hi anonymous,
    you just need to add the VersionLinkFilename computed field to your list (its schema is in the posting) and you will have the links with the proper parameters (formatted already) in any view (filter or unfiltered) which displays that field.

    ReplyDelete
  5. Hi Stefan,
    Thanks for a very good artikel!
    How to put standard.aspx?IncludeVersions=TRUE into the sharepoint link from the standard view?
    (is it possible to open the site withe the standard.aspx?IncludeVersions=TRUE as default!)
    Thanks anyway!
    Henrik Frølund (hf@profil-data.dk)

    ReplyDelete
  6. Hi Henrik,
    I can think of two a bit ugly methods which may allow you to have the IncludeVersions parameter by default. The first one is to load the LVWP in an iframe - the method is described here - http://www.pathtosharepoint.com/CrossSite/default.aspx. You can use this approach in the following way - 1st create an extra view in your list - say standard2.aspx. Then you need to minimize the existing LVWP on the page and add a page viewer web part. The page viewer should be configured as described in the article above so that it loads the standard.aspx page but with the IncludeVersions=TRUE query parameter in the URL.
    The second possible method is to use a custom HttpModule for URL rewriting using the HttpContext.RewritePath method as described here: http://msdn.microsoft.com/en-us/library/sa5wkk6d.aspx (this is more complex though).

    ReplyDelete
  7. Hi Stefan,
    Thanks for the clear explainations!

    As far as I can check this works only with listview webparts and not with dataview webparts (inserted via SP Designer). I need this same functionality cross-site (aggregate all - major version - documents from sub-sites).

    Do you know if there are any possibilities for this?

    Thanks and best regards,

    Geert-Jan

    ReplyDelete
  8. Hi Geert-Jan,
    Unfortunately, as far as I know, there is no way using the object model to retrieve all versions (or all major versions only) of the list items cross-list or cross-site with a single query. You can execute aggregate (cross-list/cross-site) queries using the SPWeb.GetSiteData method, but I don't think that you can use it to fetch item/document versions.

    ReplyDelete
  9. Hi,

    I have a scenario where in I am working on tasks list and have Comments column which display comment made by user.

    What I am trying to achieve is whenever user create comment it should be display in data view webpart with date and time comment created and Comment itself.

    right now its displays all the comments made at different time together and not displaying date time also.

    could you please give any hint on this?

    ReplyDelete
  10. Hi Anonymous,
    it didn't become quite clear for me how you got the versions of the "Comments" field displayed all together (using the IncludeVersions URL parameter or some other way ...). What SharePoint does in the standard edit form of lists with note fields with the "append changes" option is to render the AppendOnlyHistory web control. Which uses the object model to iterate all versions of the current item concatenating the values of the note field itself and the values of the "Created" property of the list item version which corresponds to the "Modified" field of the list item. So, in case you happen to be able to display the different list item versions in the web part you should use some custom XSL to combine the values of the "Comments" and the "Modified" fields. Let me know if this is the case you have or give me some more details about your setup.
    Greets
    Stefan

    ReplyDelete
  11. Hi Mitchel,
    Do you mean VB.NET or VBA - I have the code in C# only, but I think it would be fairly trivial to convert it to VB.NET - there are online convertors and the .NET Reflector utility can be used as well. Although I am not sure that any of it will be usable with VBA.

    Greets
    Stefan

    ReplyDelete
  12. Error is at FileInformation fileInformation = ClientOM.File.OpenBinaryDirect(clientContext,version.Url); line. Its not reading the file gives server relative url error and when I passed the server relative url by doing some tric like adding "/" before version url or putting the absolute URL then it gives file not found error...

    ReplyDelete
    Replies
    1. Hi,

      The client OM uses an HTTP POST request which for some reason doesn't work for the _vti_history virtual paths.
      You can instead directly issue an HTTP GET request and get the file the old fashioned way like this:

      private static string GetFile2 (string serverUrl, string serverRelativeUrl)
      {
      HttpWebRequest req = (HttpWebRequest)WebRequest.Create(serverUrl + serverRelativeUrl);
      using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
      {
      using (System.IO.Stream stream = response.GetResponseStream())
      {
      return new System.IO.StreamReader(stream).ReadToEnd();
      }
      }
      }

      If you're using credentials which are different from the executing account, make sure that you are providing these to the HttpWebRequest.Credentials property.

      Greetings
      Stefan

      Delete
  13. Hi Stefan,

    I loved your suggestion and method of adding versions to a list view using the ?IncludeVersions=TRUE mentod.

    The issue I am facing is that it works only on one view, if I create a new view and append the ?IncludeVersions=TRUE to the URL it doesnt seem to show the versions. The new view is a copy of the one on which it work, Any Ideas...

    -Manoj

    ReplyDelete
  14. Hi Manoj,
    I couldn't reproduce your issue although I have seen this not working on some installations of MOSS (for some lists, etc.) 2007.
    Do you have this for a document library or a non-library list.
    Also note that the trick doesn't work in cases when you have a multiple lookup field in the view.

    Greetings
    Stefan

    ReplyDelete
  15. Hi Stefan,
    Nice Article, but is it possible to do this in SP2013 Designer without Visual Studio? Where do I need to add XML schema to?

    ReplyDelete