Stream download SPFile from WCF service

Written by jamin. Posted in SharePoint 2010

Couple of gotchas in this one. I was using the MultipleBaseAddressWebServiceHostFactory data service factory to create a REST service.

  • The SPFile must be opened from the containing folder – SPFolder.Files[SPFile.Name]
  • The SPFile wont stream directly from SharePoint. Here it is converted to a byte array, then back to a stream.

The code:

public Stream DownloadFile(string url)
{
    try
    {
        Stream stream = null;

        SPSecurity.RunWithElevatedPrivileges(delegate()
        {
            using (SPSite site = new SPSite(url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPFile file = web.GetFile(url);
                    SPFolder folder = web.Folders[file.ParentFolder.Name];
                    file = folder.Files[file.Name];

                    var context = WebOperationContext.Current.OutgoingResponse;
                    context.ContentType = "application/octet-stream";
                    context.Headers.Add("Content-Disposition", "attachment; filename=" + file.Name);

                    var bytes = file.OpenBinary();
                    stream = new MemoryStream(bytes);
                }
            }
        });
        return stream;
    }
    catch (Exception ex) { return null; }
}

Multiple JavaScript web parts on the same SharePoint page – Namespacing

Written by jamin. Posted in JavaScript, SharePoint 2010, SharePoint 2013

Say you want to add multiple web parts to the same page, each containing JavaScript with global variables and functions. To avoid conflicts, a technique called Single Object Namespacing can be used to define a JS object and then add variables or functions as properties of this object.

To avoid conflicts, this object can be the unique ID of the web part.

//define the namespace object
var <%=ClientID%> = {};

//add a global variable property to the object
//also set its value to a variable from the web part's user control
<%=ClientID%>.animal = "<%=_animal %>";

//to set a JS variable as a boolean, retrieve the text value "True"
//from the user control and compare with another "True" string 
<%=ClientID%>.isAnimal = "<%=_isAnimal %>" == "True";

//add more properties
<%=ClientID%>.walk = function() {
  //do something
};

//call namespace function
<%=ClientID%>.walk();

The source of the page will end up looking similar to:

var ctl00_m_g_3a47a8cd_bfb0_49d0_8296_df75ce865423_ctl00 = {};

ctl00_m_g_3a47a8cd_bfb0_49d0_8296_df75ce865423_ctl00.animal = "Dog";

ctl00_m_g_3a47a8cd_bfb0_49d0_8296_df75ce865423_ctl00.isAnimal = "True" == "True";

etc....

Nintex Workflow – Query large list and dealing with XML result

Written by jamin. Posted in Nintex Workflow

When querying a list and having a large number of results returned, the danger of query throttling is always present and must be carefully considered to ensure workflows or applications will be able to scale. The default query limit is 5000 items returned for normal users, however this can be changed by farm admins so it is possible that you may be unknowingly dealing with a smaller query limit. Something to keep in mind.

The solution is to add a row limit to your queries and utilise the paging query option. Vadim Tabakman has a fantastic post and UDA available for download which addresses this problem so I won’t go into it in detail, but here are some of the nuances I encountered while setting this up.

  • Logging XML to the history list won’t display because the browser will try to render it. Instead, use the Send Notification action to create a text file, fill it with the XML and have it sent via email.
  • Values can be easily pulled out of the response XML with an XPath expression. The query XML action below will fill the collection variable with values from the Next review field.

  • SharePoint is picky with the SOAP request. The tags need to be ‘doubled up’ except for rowLimit. Doubling up this tag will break the paging. This was my Call web service – GetListItems SOAP request:
    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
        <soap:Body>
            <GetListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">
                <listName>{WorkflowVariable:ListName}</listName>
                <viewName></viewName>
                <query><Query>{WorkflowVariable:Query}</Query></query>
                <viewFields><ViewFields>{WorkflowVariable:ViewFields}</ViewFields></viewFields>
                <rowLimit>{WorkflowVariable:RowLimit}</rowLimit>
                <queryOptions><QueryOptions>
                     <IncludeMandatoryColumns>False</IncludeMandatoryColumns>
                     <Paging ListItemCollectionPositionNext="{WorkflowVariable:Paging}"></Paging>
                </QueryOptions></queryOptions>
                <webID></webID>
            </GetListItems>
        </soap:Body>
    </soap:Envelope>
    
  • PortalSiteMapProvider list driven cross site collection navigation

    Written by jamin. Posted in CodePlex, SharePoint 2010

    One thing SharePoint is missing out of the box is a centrally managed cross site collection navigation. People have solved this using various methods but everything I’ve seen has not felt like a well rounded solution which is easy to install and manage from the front end. This is my attempt.

    This blog post will serve as the documentation and install guide for the project hosted at CodePlex : https://portalnav.codeplex.com

    Quick overview

    • Navigation site data is stored in a SharePoint list.
    • GetChildNodes is overridden in PortalSiteMapProvider to read from the list and build up all nodes in the navigation.
    • Activate a feature in a site collection to override the TopNavigationDataSource control and change the navigation source.

    Installation

    Adding and activating the WSP will create the navigation list and add a site map provider entry to web.config files. The location of the list is hard coded to be http://<MachineName>. If your content web app has a different address you will need to change this value in the feature event receivers, and again in CustomNavigation.cs when the list is queried. If your content web app URL is the same as the machine name you are installing the solution from (most single server farms), nothing will need to be changed.

    Build up your navigation by adding items to the Navigation list. A parent navigation item can be selected and the order of the navigation can be specified.

    NavigationList

    Next activate the site feature Custom Top Navigation in site collections where you want the custom navigation to be displayed. The new navigation will now be displayed! All very easy..

    Navigation

    The solution contains additional CSS to hide the Home navigation item. If you know how to fix this without hiding it through CSS, please let me know! Without the CSS, this happens:

    homehome

    SharePoint 2010 list item version history on display form

    Written by Ben. Posted in SharePoint 2010

    If you are frequently referring to the version history of a SharePoint 2010 list item, having it shown on the display form makes everything just that little bit easier.

    To achieve this I’ve created a visual web part containing an iFrame to display the version history, which is then added to the DispForm.aspx page.

    Add the HTML iFrame code to the user control.

    And in Page_Load we put together all the arguments to display the Versions.aspx page.

    
    protected void Page_Load(object sender, EventArgs e)
    {
        string qID = Page.Request.QueryString["ID"];
    
        if (!String.IsNullOrEmpty(qID))
        {
            SPWeb web = SPContext.Current.Web;
            SPListItem listItem = SPContext.Current.ListItem;
            int itemID = listItem.ID;
            string list = listItem.ParentList.ID.ToString();
            string fileName = listItem.Url;
            string source = listItem.ParentList.DefaultView.Url;
            string url = web.Url + "/_layouts/Versions.aspx?list={" + list + "}&ID="
            + itemID + "&FileName=" + fileName + "&Source=" + source + "&IsDlg=2";
    
            historyFrame.Attributes.Add("src", url);
            historyFrame.Attributes.Add("width", "590");
            historyFrame.Attributes.Add("height", "100%");
            historyFrame.Attributes.Add("scrolling", "no");
            historyFrame.Attributes.Add("frameborder", "0");
        }
    }
    

    Now add the web part to the list DispForm.aspx page and enjoy your easily accessible version history!

    Large SharePoint lists and fine-grained permissions

    Written by Ben. Posted in SharePoint 2010

    When applying a workflow to a business process, a common requirement will be to change the list item’s permissions at different stages of the workflow – such as various approval statuses. While this seems straightforward enough, with the obvious solution to have the workflow set the item’s permissions so, for example:

    • The user responsible for sending the item for approval can edit the item prior to sending for approval.
    • Once the item is sent for approval, only approvers can edit the item.
    • Once approved, no-one can edit the item except for admins/owners.

    This works well with small lists, however once the number of items in the list reaches about 2000, your entire SharePoint farm will start to run into performance issues. The problem relates to the number of security scopes in the list. Each list item with fine-grained permissions adds one security scope to the list, and each scope adds more SQL queries and fetches more data which will soon overwhelm your database server.. lagging the entire farm!

    More details about fine-grained permission limits:

    So what is the solution to this problem? Well, there isn’t one… The TechNet white paper linked above hints at a workaround to control edit permissions using the SPEventReceiverType.ItemUpdating and SPEventReceiverType.ItemUpdated methods, creating your own security layer based off the item’s metadata.

    So lets try it.

    In this case we are checking “Status” field and then allowing or cancelling the event based off this value. Create an event handler and override ItemUpdating to do something based on the status…

    public override void ItemUpdating(SPItemEventProperties properties)
    {
        if ((properties.ListItem.FileSystemObjectType != SPFileSystemObjectType.Folder))
        {
            switch (properties.ListItem["Status"].ToString())
            {
                case "Approved":
                    CheckApproved(properties);
                    break;                    
                case "Waiting for review":
                    CheckOtherStatus(properties);
                    break;
                case "Waiting for approval":
                    CheckWaitingApproval(properties);
                    break;
            }
        }            
    }
    

    If the item’s “Status” is “Approved”, only admins and Owners can perform the edit…

    public void CheckApproved(SPItemEventProperties properties)
    {
        SPWeb web = properties.Web;
        SPGroup ownerGroup = web.AssociatedOwnerGroup;
        bool inGroup = web.IsCurrentUserMemberOfGroup(ownerGroup.ID);
        bool siteAdmin = web.UserIsSiteAdmin;
        bool webAdmin = web.UserIsWebAdmin;
    
        if (!inGroup && !siteAdmin && !webAdmin)
        {
                properties.RedirectUrl = properties.WebUrl + "/documents/noedit.aspx";
                properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
        }            
    }
    

    If the action is cancelled a page is displayed in a pop-up.