Developing JSLink SharePoint forms with workflows and associated issues – Part 1

I’ll start by saying I am not a fan of workflows, I find they generally introduce a lot of unnecessary problems, I much prefer to just develop a proper system with backend code and not some pointy clicky workflow BS.

However we can’t alway choose which technologies we develop a solution in, sometimes inappropriate technologies a thrust upon us from above.

I am a fan of JSLink though and in my opinion it’s a much better  forms solution in SharePoint than InfoPath ever was, I have seen some truly horrifying InfoPath forms in my time and I have been trying to talk people out of using InfoPath for years so I was very happy the day it was announced that InfoPath was deprecated.

Anyway I’ve developed a few solutions using JSLink and various workflow technologies now, so I’ve decided to write a bit about some of the approaches that I have found to work quite well, I’m going to break this up into a number of articles and the first topic will be how to handle workflow tasks.

How to handle with workflow tasks

So one approach I’ve found works is instead of directing the user to the workflow task, direct them to the form for the item the task is related to (the original list item), then once the user has done what they need to do in the original item complete the workflow task via a REST web service call.

Now when you use this approach it is important that you save the original list item before you complete the task items otherwise you will get save conflicts, and this is because when you complete the workflow task it will fire up the workflow which will then alter the item you are working on.

This is where it starts getting tricky, when you save the list item you will automatically get redirected to either the list view or the “Source” url preventing your from making the necessary REST calls to complete the workflow task.

So to get around this we need to override the default redirection behaviour of the form which works like this:

Once you have overridden the default redirect behaviour and saved the form your can now complete the workflow task like this:

Note: I have not included querying of the tasks list to find the associated tasks, you will need to do this to get the id of the associated task, in my example above I have hardcoded this id as 1.

Using this approach I find is much more intuitive for the user, and we are not using the workflow task to collect information from the user we are simply using the task to indicate that there is action required of the user, any data that is collected from the user is captured in the original list item where it is needed.

Note: In these examples I have not given much consideration to code structure and method breakdown, however these are very important elements of any application, and you should give careful consideration to this when developing your applications.

Load Workflow Class: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.

I ran into an interesting issue today on one of the sharepoint servers we manage, the issue was affecting all workflows across a single site collection, the workflows would just fail immediately and gave a ‘Failed to Start (Retrying)’ message, when I looked at the logs I found these errors:

Load Workflow Class: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.   
 at System.ThrowHelper.ThrowKeyNotFoundException()   
 at System.Collections.Generic.Dictionary`2.get_Item(TKey key)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.IsConfigForSite(SPSite site)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.GetWorkflowConfurationSection(SPSite site, String section)   
 at Microsoft.SharePoint.Workflow.SPWinOeHostServices.EnsurePluggableServices(SPSite site, SPWorkflowExternalDataExchangeServiceCollection services, ExternalDataExchangeService existingServices)   
 at Microsoft.SharePoint.Workflow.SPWinOeHostServices..ctor(SPSite site, SPWeb web, SPWorkflowManager manager, SPWorkflowEngine engine)     -
-- End of inner exception stack trace ---   
 at System.RuntimeMethodHandle._InvokeConstructor(Object[] args, SignatureStruct& signature, IntPtr declaringType)   
 at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)   
 at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.LoadPluggableClass(String classname, String assembly, Object[] parameters)

 

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.   
 at System.ThrowHelper.ThrowKeyNotFoundException()   
 at System.Collections.Generic.Dictionary`2.get_Item(TKey key)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.IsConfigForSite(SPSite site)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.GetWorkflowConfurationSection(SPSite site, String section)   
 at Microsoft.SharePoint.Workflow.SPWinOeHostServices.EnsurePluggableServices(SPSite site, SPWorkflowExternalDataExchangeServiceCollection services, ExternalDataExchangeService existingServices)   
 at Microsoft.SharePoint.Workflow.SPWinOeHostServices..ctor(SPSite site, SPWeb web, SPWorkflowManager manager, SPWorkflowEngine engine)     -
-- End of inner exception stack trace ---   
 at System.RuntimeMethodHandle._InvokeConstructor(Object[] args, SignatureStruct& signature, IntPtr declaringType)   
 at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)   
 at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.LoadPluggableClass(String classname, String assembly, Object[] parameters)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.GetService(SPWorkflowAssociation association, SPWorkflowEngine engine)   
 at Microsoft.SharePoint.Workflow.SPWorkflowManager.RunWorkflowElev(SPWorkflow workflow, Collection`1 events, SPWorkflowRunOptionsInternal runOptions)

When I googled these errors I found this article and I tried his approach but no luck, so I opened up .NET Reflector and had a look at the IsConfigForSite method on the SPWorkflowManager class and found the following:

.net-reflector

The exception is being thrown when it’s trying to access the IisSettings dictionary using the SPUrlZone.Default enum, so I opened up a powershell console and had a look at what was in the IisSettings dictionary:

$site = Get-SPSite http://mysite
$site.WebApplication.IisSettings

This showed that there was no entry for the default zone in the IisSettings dictionary, initially I thought we just needed to reconfigure the alternate access mappings and add an entry for the default zone but this had no affect.

So I started discussing this issue with one of my colleagues who had worked on some issues with this sever the previous week. I knew he had made some pretty significant changes, and as it turned out one of those changes was he had extended the web application to the intranet zone and then deleted the previous web application.

This was the root cause of the problem, this is why there was no default entry in the IisSettings dictionary, so I extended the web application to the default zone and the workflows started working again.

Upgrading User Profile Service Application from 2010 to 2013

The first thing to do is make sure you environment is setup properly and all your service account and setup and configured correctly, and good point of reference is this Spencer Harbar’s guide to User Profile Service: http://www.harbar.net/articles/sp2010ups.aspx

Before upgrading your existing databases I recommend creating a new User Profile Service Application to confirm that you have your environment configured correctly, and make sure the User Profile Service Application is functioning correctly and both the ‘User Profile Service’ & ‘User Profile Synchronization Service’ services are started.

I scripted this in PowerShell using this script: https://gallery.technet.microsoft.com/Create-a-User-Profile-c2136dc0

Once you have a working User Profile Service Application it’s time to start the upgrade process, first delete the User Profile Service Application you just created and tick the ‘Delete data associated with the Service Applications’ check box, then backup and restore your Profile, Sync & Social databases from the 2010 environment to the new 2013 environment.

You will also need to export the encryption key from the 2010 environment using miiskmu.exe which is located in C:\Program Files\Microsoft Office Servers\14.0\Synchronization Service\Bin, just run this executable and follow the prompts you will need to enter the farm account credentials, then copy the exported key file to the 2013 environment.

Then using the same PowerShell script as before create the User Profile Service Application, make sure the database names in the script match the newly restored 2010 databases, the databases will be upgraded in the process of creating the User Profile Service Application.

Don’t be surprised if the ‘User Profile Synchronization Service’ service doesn’t start properly, you will need to stop both the ‘User Profile Service’ & ‘User Profile Synchronization Service’ services anyway to import the encryption key.

To import the encryption key you will need to log onto the server as the farm account, and run the command prompt as administrator, then run the following command:

miiskmu.exe /i "c:\path to you key file" {0E19E162-827E-4077-82D4-E6ABD531636E}

Note: The Guid in this command is the same everywhere, still not sure what it means or why you need it. Also if you do not log onto the server as the farm account for this process then your synchronization service will not start and will log errors like ‘User Profile Application: SynchronizeMIIS encounters an exception: System.NullReferenceException: Object reference not set to an instance of an object.’ to the ULS.

If all goes well then you should get a message window popup say operation was successful.

Now restart the ‘User Profile Service’ & ‘User Profile Synchronization Service’ services, and that’s it you should be up and running at this stage.

Once that is setup you might want to add the service proxy to the default group, which is one thing the above script does  not do, you can add the proxy to the default group via powershell like this:

$proxy = Get-SPServiceApplicationProxy -Identity 'guid of service proxy'

$group = Get-SPServiceApplicationProxyGroup -Default

Add-SPServiceApplicationProxyGroupMember $group.Name -Member $proxy

References:
http://community.spiceworks.com/topic/492436-starting-ups-synchronization-service-sp2013
https://social.technet.microsoft.com/Forums/en-US/e2cab627-9e8f-4f6e-91b2-fa873ce50940/user-profile-service-application-upgrade-issues
https://support.office.com/en-ie/article/Microsoft-Office-Servers-2010-FAQ-ReadMe-02aa8131-abd8-41e5-9dac-f317c0916f64
http://www.harbar.net/articles/sp2010ups2.aspx

Powershell, adding and connecting webparts

Recently had an issue with a powershell script I was writing for a sharepoint project I was working on, I needed to add two webparts to a page and connect them, they were an XsltListViewWebpart and a Nintex List Form webpart and I was following these articles:

http://social.msdn.microsoft.com/Forums/sharepoint/en-US/1cb661a0-b42f-4ec8-a42a-9a5d1fe1dce1/connect-xslt-list-view-and-query-string-filter-web-part-via-powershell?forum=sharepointdevelopment
http://troyvssharepoint.blogspot.in/2012/08/web-parts-connections-via-powershell.html

I could add the webparts to the page without any problems, but when I tried to connect them the connection wouldn’t save properly, and the problem turned out to be the i just needed to re-get the SPLimitedWebPartManager after adding the webparts, then create the connection.

And here is an example script:

add-pssnapin Microsoft.Sharepoint.Powershell
$url = http://mysiteurl
$web = Get-SPWeb $url
$wpGallery = $web.ParentWeb.Lists["Web Part Gallery"]
$wpManager = $web.GetLimitedWebPartManager($web.Url + “pages/test.aspx”,[System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)

Write-Host ‘clearing page…’
while ($wpManager.WebParts.count -gt 0)
{
    $wpManager.DeleteWebPart($wpManager.WebParts[0])
}
$recordList = $web.Lists['Record List']
$viewFields = New-Object System.Collections.Specialized.StringCollection
$viewFields.Add(“RecordID”)
$view = $recordList.Views | where { $_.title -eq ‘RecordByID’ }
if($view -eq $null){
    Write-Host ‘creating RecordByID view…’
    $parameterBindings = ‘’
    $viewQuery = ‘{RecordID}’
    $view = $recordList.Views.Add(“RecordByID”, $viewFields, $viewQuery, 100, $True, $False, “HTML”, $False)
    $view.ParameterBindings = $parameterBindings;
    $view.Update()
}

Write-Host ‘creating filtered list view webpart…’
$recordByID = New-Object Microsoft.SharePoint.WebPartPages.XsltListViewWebPart
$recordByID.ChromeType = [System.Web.UI.WebControls.WebParts.PartChromeType]::TitleOnly
$recordByID.Title = “Record List”
$recordByID.ListName = ($recordList.ID).ToString(“B”).ToUpper()
$recordByID.ViewGuid = ($view.ID).ToString(“B”).ToUpper()
$recordByID.ParameterBindings = $parameterBindings;
$recordByID.TitleUrl = ‘$url/’ + $view.Url
$recordByID.WebId = $recordList.ParentWeb.ID
$wpManager.AddWebPart($recordByID, “Header”, 1)

Write-Host ‘creating nintex list form webpart…’
$wpl = $wpGallery.Items | where {$_.Title -eq ‘$Resources:NFResource,WebPart_List_Form_Title;’} 
$xmlReader = New-Object System.Xml.XmlTextReader($wpl.File.OpenBinaryStream()); 
$errorMsg = “” 
$webPart = $wpManager.ImportWebPart($xmlReader, [ref]$errorMsg) 
$webPart.Title = ‘List Form’;
$webPart.Mode = ‘Edit’;
$wpManager.AddWebPart($webpart,”Header”,1)

##### you must re-get the webpart manager #####
$wpManager.Dispose()
Write-Host ‘refreshing webpart manager…’
$wpManager = $web.GetLimitedWebPartManager($web.Url + “pages/test.aspx”,[System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
$consumerWebPart = $wpManager.WebParts | where {$_.Title -eq ‘List Form’}
$providerWebPart = $wpManager.WebParts | where {$_.Title -eq ‘Record List’}

Write-Host ‘getting connection points…’
$providerConnectionPoints = $wpManager.GetProviderConnectionPoints($providerWebPart)
$consumerConnectionPoints = $wpManager.GetConsumerConnectionPoints($consumerWebPart)
# find matching interfaces
foreach($pc in $providerConnectionPoints){
    $consumerCon = $consumerConnectionPoints | where { $_.InterfaceType -eq $pc.InterfaceType }
    if($consumerCon -ne $null){
       $providerCon = $pc
        break
    }
}

Write-Host ‘connecting webparts…’
$newCon = $wpManager.SPConnectWebParts($providerWebPart,$providerCon,$consumerWebPart,$consumerCon)
$wpManager.Dispose()
$web.dispose()

Write-Host ‘done’