How to add an “Attachments” button on a SharePoint form without using Infopath

Why traditional SharePoint form design and not InfoPath?

For a very long time Infopath has been hailed as one of the better ways to develop SharePoint forms. It’s undeniable that InfoPath is fast, in many ways flexible and easy for more technical business users to maintain.

All of that said in 2014 Microsoft dropped the following bombshell:

The InfoPath 2013 application remains the last version to be released and will work with SharePoint Server 2016.

Source: Official MSDN blog entry.

As it appears InfoPath is nearing the end of its life we’ve begun developing SharePoint forms in a more traditional manner. We’ve used SharePoint 2013’s web compatibility to its fullest by using JavaScript for most of our interactions and ASP where needed. The outcomes have been very, very surprising. We’ve found we can do far more than we ever could with InfoPath. Forms are responsive making them mobile compatible (using our own CSS), adaptive to what the user enters and they look amazing.

One thing we have found is that users are still used to having an attachments button on their forms and haven’t adapted well to having to go to the ribbon to add attachments. As a reminder, this is the field that InfoPath would add to your SharePoint forms to allow users to easily add attachments:

SharePoint and InfoPath Attach file button

How do I add an attachments button to a traditional SharePoint form?

To add the attachments button to a traditional SharePoint form first open the library in SharePoint designer. For whatever reason SharePoint locks down the default ASPX form (typically titled NewForm.aspx) and creating a new one (and making it the default) will allow you to customize your form as you desire.

Once you have created a new form add the following to the page to add an equivalent button that duplicates the functionality of InfoPath:

<input id="btnAttachFile" onclick="javascript:UploadAttachment();" type="button" value="Click here to attach a file">

This will present a basic button to attach files to your form. If you wish to further customize this to also list the attached files below it, the following should be used:

 <td valign="top" class="ms-formbody">
	 <input id="btnAttachFile" onclick="javascript:UploadAttachment();" type="button" value="Click here to attach a file" />
	 <SharePoint:FormField runat="server" id="AttachmentsField" ControlMode="New" FieldName="Attachments" __designer:bind="{ddwrt:DataBind('i','AttachmentsField','Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Attachments')}"/>
	 <script>
      var elm = document.getElementById("idAttachmentsTable");
      if (elm == null || elm.rows.length == 0)
      document.getElementById("idAttachmentsRow").style.display='none';
     </script>
 </td>
<tr id="idAttachmentsRow">
</tr>
<xsl:if test="$dvt_1_automode = '1'" ddwrt:cf_ignore="1">
	<tr>
		<td colspan="99" class="ms-vb">
			<span ddwrt:amkeyfield="ID" ddwrt:amkeyvalue="ddwrt:EscapeDelims(string(@ID))" ddwrt:ammode="view"></span>
		</td>
	</tr>
</xsl:if>

The final outcome will look something like the following (depending on how you adapt it for your own forms):

SharePoint Attach File Form Button

How to download files from a SharePoint Online Document Library

As SharePoint Online captures more and more market share people are going to start doing more and more with it.

One of the core challenges that I’m noticing is the struggle for developers in transitioning from server side code to using the client side object model (CSOM). There are a lot of ways to accomplish and this is just one example in many. This example assumes that the source document library has a column called Status. It will download files from that document library where the Status is equal to New and after download all of these files it will then update the status of those files to Downloaded.

internal class Program
{
    private static void Main(string[] args)
    {
        const string username = @"username@sharepointdomain.com";
        const string password = @"password";
        const string downloadLocation = @"C:\TestDownload";
        const string sourceSite = @"https://testnet.sharepoint.com/documentlibrary";
        const string documentLibrary = @"sourceLibrary";

        var securePassword = new SecureString();

        // SharePoint Online requires that passwords be converted into a Secure String, we do this below
        foreach (var c in password.ToCharArray())
        {
            securePassword.AppendChar(c);
        }

        // check if our download directory exists, if not then we create it
        if (!Directory.Exists(downloadLocation))
        {
            Console.WriteLine("Download location didn't exist, creating folder {0}", downloadLocation);
            Directory.CreateDirectory(downloadLocation);
        }

        using (var ctx = new ClientContext(sourceSite))
        {
            // This adds our credentials to the query, note that it's using our securePassword (the securestring version of our password)
            ctx.Credentials = new SharePointOnlineCredentials(username, securePassword);
                

            // This builds a request for SharePoint to tell us we only wish to retrieve files that have a Status (column) of New
            var query = new CamlQuery
            {
                ViewXml = @"New"
            };

            // This adds the list that we're planning to use into the query, it's best to do this by GUID but using the document library name here for ease of use and understanding
            var list = ctx.Web.Lists.GetByTitle(documentLibrary);

            // this tells the query that we only want items that match our caml query we built earlier - makes for a much smaller payload and faster code
            // that costs a lot less to execute (as we're only requesting the files we need, not everything and then itterating over it)
            var collListItem = list.GetItems(query);

            // The below tells SharePoint what data we're requesting from it. Anything that you wish 
            // to be used programmatically needs to be included here.
            ctx.Load(
                collListItem,
                items =>
                    items.Include(
                        item => item.Id,
                        item => item.DisplayName,
                        item => item.File.Name,
                        item => item.HasUniqueRoleAssignments,
                        item => item["Title"],
                        item => item["Status"],
                        item => item["FileRef"]));

            // This tells SharePoint to execute the current query (the one we just loaded)
            ctx.ExecuteQuery();

            Console.WriteLine("Successfully connected to SharePoint Online");

            foreach (var listItem in collListItem)
            {
                var fileInfo = Microsoft.SharePoint.Client.File.OpenBinaryDirect(ctx, (string) listItem["FileRef"]);
                var fileName = Path.Combine(downloadLocation, listItem.File.Name);

                // If the file already exists locallay, don't download it again. Alternatively you could change this logic
                // to delete and redownload the file..
                if (System.IO.File.Exists(fileName))
                {
                    Console.WriteLine("{0} ({1}) has already been downloaded previously.", listItem.DisplayName, listItem.File.Name);
                    continue;
                }

                Console.WriteLine("Downloading {0}", listItem.DisplayName);

                using (var fileStream = System.IO.File.Create(fileName))
                {
                    // if there isn't any file information available for download move to the next file
                    if (fileInfo == null) continue;

                    fileInfo.Stream.CopyTo(fileStream);
                }

                // this sets our status to in progress and adds it into the queue for future processing
                listItem["Status"] = "Downloaded";
                listItem.Update();
            }

            // this executes all of our status updates
            ctx.ExecuteQuery();

            Console.WriteLine("All files have been downloaded, press any key to continue...");
            Console.ReadKey();
        }
    }
}

Reversing a string in Python with Performance Benchmarking

Recently I’ve been putting a lot of time into brushing up my Python knowledge for big data projects and operations. For a task that’s so simple I’ve found reversing a string can be quite enlightening for how to better “think in Python”.

As an example, here’s how I would have previously reversed a string before really digging into the Python language:

def reverse(string): 
    begin = 0 
    end = len(string) - 1 
    strlist = [i for i in string] 
    while(begin < end): 
        temp = strlist[begin] 
        strlist[begin] = strlist[end] 
        strlist[end] = temp 
        begin += 1 
        end -= 1 
    return ''.join(strlist)

print reverse('flash')

Quite less than ideal!

After digging a bit further I learnt about slice notation in Python – and it’s amazing. This StackOverflow question serves as a good primer, here’s the core answer from that question for future reference as well:

a[start:end] # items start through end-1
a[start:]    # items start through the rest of the array
a[:end]      # items from the beginning through end-1
a[:]         # a copy of the whole array

There is also the step value, which can be used with any of the above:

a[start:end:step] # start through not past end, by step

The key point to remember is that the :end value represents the first value that is not in the selected slice. So, the difference beween end and start is the number of elements selected (if step is 1, the default).

The other feature is that start or end may be a negative number, which means it counts from the end of the array instead of the beginning. So:

a[-1]    # last item in the array
a[-2:]   # last two items in the array
a[:-2]   # everything except the last two items

So what does this mean for reversing a string? Actually, quite a lot – that means we can now take our original statement above and re-write it simply as:

"flash"[::-1]

But how about performance? Python is commonly used on big data after all. Let’s introduce a couple of other ways to accomplish this task in Python and then benchmark them against each other:

Another Python specific approach for this task would be the reversed() function

''.join(reversed("flash"))

We could also use a generator function, one of the more friendly ways of writing this as it reads as it performs – join all the i-th-s elements of the string where i goes from the len of the string to zero.

def reverse(string):
    return ''.join(string[i] for i in range(len(string) - 1, -1, -1))

We could also use a deque object:

from collections import deque

def reverse_deque(string):
    d = deque(string)
    d.reverse()
    return ''.join(d)

But what about performance? Using timeit let’s quickly benchmark these against each other:

Original function

In [250]: %timeit reverse_codingo(“reverse”)
100000 loops, best of 3: 3.61 µs per loop

Slice Operator

In [243]: %timeit “reverse”[::-1]
1000000 loops, best of 3: 310 ns per loop

Reverse Function

In [241]: %timeit reverse(“reverse”)
100000 loops, best of 3: 3.98 µs per loop

Deque Reverse Function

In [244]: %timeit reverse_deque(“reverse”)
100000 loops, best of 3: 2.61 µs per loop

The clear winner in speed appears to be the slice operator, also the one which is syntactically the most simple once you’re familiar with it. Definitely an operator worth learning more about and including in the toolkit!

InfoPath Forms stop working after applying cumulative update: ‘Requested registry access is not allowed’

After recently applying the July 2015 SharePoint 2013 Cumulative update we discovered that Infopath forms or pages referring them had stopped loading throwing a generic “something went wrong” error to the screen with a correlation id. After taking a look into the logs we found the following against the Infopath form services requests:

Requested registry access is not allowed

This appears to be a known issue within previous SharePoint cumulative updates (previously documented in the March cumulative update). There’s good writeups about this issue on Todd Klint’s always useful blog and on the accepted answer to this MSDN question. The problem appears to be the same in the July 2015 update – SharePoint updates the registry removing permissions for service accounts causing problems with Infopath and other areas (in our particular case BDS stopped working as well).

To resolve the issue you need to re-grant permissions for the user WSS_WPG to the registry group HKLM/Software/Office Server/15.0. After doing this power cycle the server and everything will function as expected again.

An error occurred. Detailed message: 1 conflict prevents checkout when doing a GIT pull request in Visual Studio

I’ve noticed something interesting happening in Visual Studio 15 when I move between environments and try to do a pull request.

I’m able to see the incoming commits queue as well as the content within them but when I go to do a pull request I’ll receive the error:

An error occurred. Detailed message: 1 conflict prevents checkout

Although this is usually quite an easy problem to resolve for some reason Visual Studio isn’t showing the diff tool and allowing me to resolve the conflicts.

Thankfully as we’re using git for our version control backend this is quite easy to resolve in the backend.

To overcome this first open a new command line window for the repository where the conflict is.

First navigate to the synchronization page which shows you incoming and outgoing commits. In the window that appears click Actions and then Open in Command Prompt.

Open GIT command prompt window

You then want to perform a git status command to confirm that you have outstanding changes to pull from the current branch.

GIT Status command prompt example

And then do a git pull to manually pulldown the latest version of the branch to your local repository. In order to do successfully perform a pull request you will also need to enter alternative access credentials. If you don’t already have these (or know them) then logging into your Visual Studio Online page, managing your profile (top right) and setting them on Security will create the credentials you need whenever using git from the console window.

 

A SharePoint Site Collection has disappeared or been deleted, how do I restore it?

So you come in of a morning and you have a whole host of e-mails from users complaining that content isn’t working for them. You visit the main page, no issues, but then you go to visit a site in another collection… and you receive a dreaded 404 error:

This error (HTTP 404 Not Found) means that Internet Explorer was able to connect to the website, but the page you wanted was not found. It’s possible that the webpage is temporarily unavailable. Alternatively, the website might have changed or removed the webpage.

Checking the recycle bin shows that the web is still active, and so it appears that there’s something wrong with the Site Collection.

Although not present in the front end thankfully there’s a cmdlet to allow you to quickly view deleted collections and then you can look to restoring them.

Get-SPDeletedSite

Which will allow you to view the deleted collections. Click here for documentation.

Once you have a list of your collections you can then restore them using

Restore-SPDeletedSite

Here’s an example we had with a client –

We identified that the services site collection had gone rogue and couldn’t be found anywhere. We’ve then run Get-SPDeletedSite to see if by chance it had been deleted, which by chance it had and showed in the results:

Example of Get-SPDeletedSite

Thankfully this means that the solution is easy – using

Restore-SPDeletedSite /services

Restored the site immediately (without any downtime or ISS recycling being necessary) and makes it available again to users:

Example of Restore-SPDeletedSite

The interesting problem is always how a collection was deleted in the first place. Interestingly enough in this particular case it looks like a user had received the automated e-mail asking them to confirm whether a SharePoint Web site is still in use, much like the following:

From: SharePoint
Sent: Wednesday, 8 July 2015 12:00 AM
To: Administrator
Subject: Confirm SharePoint Web site in use

Please follow the link below to your SharePoint Web site to confirm that it is still in use.
http://sharepoint/services/_layouts/useconfirmation.aspx

If the site is not being used, please go to http://sharepoint/services/_layouts/settings.aspx, and select “Delete This Site” to remove the Web site.

You will receive reminders of this until you confirm the site is in use, or delete it.

The user following this link and then confirming that they would like the web (in this case not a web but a collection) deleted the collection. A good lesson in always revisiting who is an administrator of what – in this case the user managed to delete a collection without  central admin!

SharePoint deployment scope, solution and features

SharePoint Features

SharePoint Features can be scoped to the Farm, Web Application, Site Collection, and Web Site level depending on the purpose of the feature. The Feature scope is determined by the setting of the Scope attribute in the Feature element defined in the feature.xml file.

A sample Feature element tag is given below:

<Feature Id="F62C96CF-79FD-44be-8882-E9BFBD199184">
Title="Feature Title"
Description="Feature Description"
Version="1.0.0.0"
Scope="Site"
Hidden="false";
</Feature>

Web Site scoped Feature (Scope=”Web”):

A Web Site scoped Feature is one that can be activated only at the individual Web site level. List templates, list instances, custom actions, event receivers, etc are the some common elements for web site scoped features. Web Site scoped features can be activated by using:

Run the following STSADM command:

stsadm -o installfeature -name FeatureFolderName –url http://servername/site/subsite

Site Collection scoped Feature (Scope=”Site”):

A Site Collection scoped Feature is one that can be activated at the site collection level and contains items that apply to the site collection as a whole (for example, content types that are shared across the site collection), as well as items that can be activated per site (for example, list instances, etc). Site Collection scoped features can be activated by:

Run the following STSADM command:

stsadm -o installfeature -name FeatureFolderName –url http://servername/site/sitecollection

Web Application scoped Feature (Scope=”WebApplication”):

A Web Application scoped Feature is one that can be activated at the web application level and contains items like administrative web application links, delegate control registrations, feature/site template association, document form registrations, etc. A farm Feature can contain, for example, links to /_layouts pages and files, /_admin pages, and other elements. Web Applicationscoped features can be activated by using:

Run the following STSADM command:

stsadm -o installfeature -name FeatureFolderName -url http://servername

Farm scoped Feature (Scope=”Farm”):

A Farm scoped Feature can be activated at the server farm level. A farm Feature contains a number of elements that are critical for implementing applications and logic anywhere within a deployment. A farm Feature can contain, for example, links to /_layouts pages and files, /_admin pages, and other elements. Farm scoped features can be activated by using:

Run the following STSADM command:

stsadm -o installfeature -name FeatureFolderName

SharePoint Solutions

SharePoint solutions are either deployed globally or targeted to a particular web application. The decision of which is made automatically by the SharePoint Solution framework depending on the contents of the solution manifest. Exception to this rule are Sandbox solutions which are managed on the site collection level.

Globally Deployed Solutions

When a solution is deployed globally, the assembly DLL file will go and sit under windows\assembly folder. All SharePoint application pools, including Central Administration’s, are recycled automatically. This can be good and bad. This is good because any GAC installed DLL that has been upgraded needs to be reloaded. This can be bad though with regards to the availability of your entire SharePoint Farm.

Web Application Targeted Solutions

When a web application targeted solution is deployed or retracted, the assembly DLL file will go and sit under the inetpub\websitename\bin folder. Only the application pools of the targeted web applications are recycled. When deploying and retracting a web application targeted solution, deploy or retract it only to those web applications that will use it … thus preventing unnecessary recycling of application pools.

Sandbox Solutions

Sandbox solutions are deployed to the site collection. This new mechanism has been introduced in SharePoint 2010 to provided more isolation between deployed components. Sandbox Solutions deployment does not require application pool recycle and does not allow deploying DLLs into the GAC, as everything is stored in content database where site collection resides. These solution also provide much more restrictive execution model and limited access to SharePoint API.

PowerShell Code to deploy solutions (WSP)

PowerShell to deploy the WSP to the GAC on server and deploy to all URLS

Add-SPSolution -LiteralPath $path
install-spsolution -Identity $solution  -GACDeployment

PowerShell to deploy the WSP to the GAC on server and deploy only to 1 specific web application (not in bin folder)

Add-SPSolution -LiteralPath $path
install-spsolution -Identity $solution -WebApplication $oURL  -GACDeployment

PowerShell to deploy to specific webapplication at bin folder on server

Install-SPSolution -Identity $solution -WebApplication $webApp

PowerShell to deploy to all the webapplication at their bin folders on server

Install-SPSolution -Identity $solution -AllWebApplications

© 2016 Codingo

Theme by Anders NorenUp ↑