Monday, 31 October 2011

Specifying Sort Order When Querying the SharePoint List Web Service with CAML and jQuery (via Lists.asmx)

I had a couple of problems today with specifying the sort order of data returned from a CAML query via a jQuery AJAX call. It seemed that no matter what setting I put in, it always seemed to return an unordered resultset.

I resolved the problem by looking at the requests in Fiddler that were produced by U2U CAML query Builder. The main problem was that I wasn't correctly wrapping the CAML "Query" node in a parent "query" node. Now you only have to do this when you're doing jQuery calls directly against web services - and are shielded from this somewhat when you are calling the SharePoint client object model (for details you can look here http://msdn.microsoft.com/en-us/library/gg701783.aspx). Why I'm using web service calls directly is a discussion for another time (and I wouldn't automatically turn to using it as a preference - as described here http://msdn.microsoft.com/en-us/library/ee539764.aspx) - but I'll say for now that it is for consistency with existing code (The client object model is using the web services underneath as well, but has batching facilities). See below for an example of a jQuery call which does sorting that worked for me. U2U CAML Query builder will also give you the valid internal name for the field.

var errorMessage;
  //Add order by if variable is set in other part of page (so it can be switched on or off on a page-by-page basis.
  if (typeof missingCategoryDisplayMode_IsOnline != 'undefined' && missingCategoryDisplayMode_IsOnline == true  ) //Variable should be set by another content query web part on page
  {
   var orderBy = "\
       \
      ";
  }

try
  {
   var contentCategoryCAML = "<?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>Content Categories</listName> \
           <query>\
           <Query xmlns=''>\
           " + orderBy +
           "</Query>\
           </query>\
           <viewFields>\
           <ViewFields xmlns='' />\
           </viewFields>\
           <queryOptions>\
           <QueryOptions xmlns=''/> \
           </queryOptions></GetListItems> \
         </soap:Body></soap:Envelope>";
   $.ajax({
    url: listUrl,
    type: "POST",
    dataType: "xml",
    data: contentCategoryCAML,
    complete: ContentCategoryResult,
    contentType: "text/xml; charset=\"utf-8\""
   });
  }
  catch (ex)
  {
   displayError("There was an error retrieving data from the Content Categories list. Please see IT support for assistance.");
   return;
  }
  
  function ContentCategoryResult(xData, status)
  {
   $(xData.responseXML).find("z\\:row").each(function () 
   {           
      var Title = $(this).attr("ows_LinkTitle");           
      arrContentCat.push(Title);  //Adding the results in an array
   });
  }


This adds items to an array that are used later for rendering. I needed a different sort order based on the page - so this was done using a seperate javascript file that is placed into a Content Editor Web Part on pages to set the sort order variable.

I also use the SharePoint client model to programatically determine the site collection path and to ensure that the list web service of the current site collection root is used (you can't determine that just by the url):

function MenuInitialize()
 {
     var clientContext = new SP.ClientContext();
        var siteColl = clientContext.get_site();
        myweb = siteColl.get_rootWeb();
        clientContext.load(myweb);
        /* Execute async required*/
        clientContext.executeQueryAsync(Function.createDelegate(this, executeRequestAndRender), Function.createDelegate(this, getFailed));
 }
 
 function executeRequestAndRender() {
  var siteCollectionUrl = myweb.get_serverRelativeUrl();
  var listWebServiceUrl = siteCollectionUrl + "/_vti_bin/lists.asmx"
  //When at the root, siteCollectionUrl is '/', but other sites have /sitecollection/sites
  listWebServiceUrl = listWebServiceUrl.replace('//', '/');
  /* When complete, we can render requests */
  RenderMenu_ContentCategoryList(listWebServiceUrl);
 }


For the SharePoint Client Object model to work of course, you should always tell Script on demand (SOD) to process the code that references the SP client object model only when the SP.js file has been loaded:

$(document).ready(function()
{
 /*Ensure that the Sp.js is loaded (using Script on Demand (SOD) SharePoint library) so the client object model functions correctly */
 SP.SOD.executeOrDelayUntilScriptLoaded(MenuInitialize, 'SP.js');

DDK

Tuesday, 18 October 2011

SharePoint 2010 - My jQuery Scripts are Broken on the Publishing Portal Thumbnails.aspx Page. All other pages are fine.

I've had this problem many times in ASP.NET but never in SharePoint 2010. Today, one of our jQuery script implementations was failing on just a couple of the SharePoint pages - in particular the Publishing Portal Thumbnails Page.

All the jQuery objects were returning null values even though it worked for all the pages in the SharePoint site. The problem is back to that golden oldie of the jQuery object shortcut of $ conflicting with the Microsoft AJAX framework. Both frameworks use the $ as an object shortcut and so this causes problems. I almost felt nostalgic getting this exception :o).

Why does it only happen on a couple of pages in SharePoint 2010? They are the only ones which have script references to the MS AJAX javascript libraries.

There are ways around this using aliases (via the jQuery.noConflict() command) - but the simplest way is to just use jQuery as a best practice when working within SharePoint 2010 -  with a find and replace of "$(" with "jQuery(" as needed. This fixed the problem.

Too easy!

DDK

Wednesday, 12 October 2011

CSS Positioning Refresher - Relative vs Absolute vs Static vs Fixed - SharePoint 2010 Branding

My current client is a media company who wanted branding done as part of a SharePoint 2010 Portal implementation project. One problem arose because their standard operating environment uses Internet Explorer 7. Our IE 7 specific issues arose because of IE7's position behaviour and it's interaction with a dynamic pop-down DIV that we were injecting with jQuery.

In IE7 (not in IE8+ or Firefox), our whole content div in SharePoint was getting pushed down by our hidden div used as part of the pop-down menu. Turns out our resolution to fix this IE 7 was to make our hidden div to use position:absolute rather than relative/static.

The critical difference between relative and absolute in particular is not how it changes the behaviour of the div - but rather how it affects the flow of OTHER elements on the page. This is particularly important when you are injecting elements into SharePoint with jQuery. The best explanation of CSS positioning (and how it affects other html elements) I've found is here:

http://www.w3schools.com/css/css_positioning.asp


RELATIVE: The content of relatively positioned elements can be moved and overlap other elements, but the reserved space for the element is still preserved in the normal flow.
ABSOLUTE:
Absolutely positioned elements are removed from the normal flow. The document and other elements behave like the absolutely positioned element does not exist (Yes! this is what we really want)

This still doesn't explain the rendering difference in IE7 vs IE8 vs Firefox apart from the fact that other browsers seem to be more forgiving when specifying your CSS positioning compared to IE7 (in Standards mode).

DDK

Wednesday, 5 October 2011

SharePont 2010 - Why is Inline Editing not working even when enabled for the current view?

One of my colleagues at Oakton had issues in that a SharePoint 2010 Custom List wasn't allowing editing inline.

Even when modifying the view and enabling the "Allow Inline" editing property of the list view - no errors, it just didn't work and the Edit icon just opened the full blown SharePoint modal dialog box as usual.



The problem was that the list view must use the Default Style for Inline editing to work.



DDK

Thursday, 8 September 2011

Fix - My SonicWall Client Keeps Prompting for a Phone Book Entry even though I'm already connected

When connecting via Telstra Mobile Broadband and attempting to connect to a SonicWall VPN with the SonicWall client, I kept getting prompted for a Dialup Phone Book entry mutliple times. Every time I connected (though not when using normal wireless from home), I had to keep attempting a connection until the username and password prompt displayed.

The fix for this problem is to ensure that the "LAN Only" drop down is selected:
1) Open the SonicWall VPN Global Client
2) Click on Peers and Choose the correct connection; Click Edit
3) Under Interface Selection, Choose "LAN Only" rather than "Automatic" in the drop down list.


Tuesday, 6 September 2011

Fix - Duet Enterprise/SharePoint 2010 Exception - "System.ServiceModel.QuotaExceededException: The size necessary to buffer the XML content exceeded the buffer quota."

When attempting to establish communications between SAP and SharePoint (using the Duet Enterprise Claims provider) today at a new client, I encountered the following exception in SharePoint (as per the ULS logs):

InnerException 1: System.ServiceModel.QuotaExceededException: The size necessary to buffer the XML content exceeded the buffer quota. Server stack trace:

at System.ServiceModel.Channels.BufferedOutputStream.WriteCore(Byte[] buffer, Int32 offset, Int32 size)
at System.Xml.XmlStreamNodeWriter.FlushBuffer()
at System.Xml.XmlBinaryNodeWriter.FlushBuffer()
at System.Xml.XmlStreamNodeWriter.GetBuffer(Int32 count, Int32& offset)
at System.Xml.XmlBinaryNodeWriter.UnsafeWriteText(Char* chars, Int32 charCount)
at System.Xml.XmlBinaryNodeWriter.WriteText(Char[] chars, Int32 offset, Int32 count)
at System.Xml.XmlBaseWriter.WriteChars(Char[] chars, Int32 offset, Int32 count)
at System.Xml.XmlBinaryWriter.WriteTextNode(XmlDictionaryReader reader, Boolean attribute)
at System.Xml.XmlDictionaryWriter.WriteNode(XmlDictionaryReader reader, Boolean defattr)
at System.ServiceModel.Channels.ReceivedFault.CreateFault12Driver(XmlDictionaryReader reader, Int32 maxBufferSize, EnvelopeVersion version)
at System.ServiceModel.Channels.MessageFault.CreateFault(Message message, Int32 maxBufferSize)
at System.ServiceModel.Channels.SecurityChannelFactory`1.ClientSecurityChannel`1.TryGetSecurityFaultException(Message faultMessage, Exception& faultException)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.ProcessReply(Message reply, SecurityProtocolCorrelationState correlationState, TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown
at [0]:
at Microsoft.SharePoint.BusinessData.SystemSpecific.Wcf.WcfSystemUtility.Execute(Object[] args)
at Microsoft.SharePoint.BusinessData.SystemSpecific.Wcf.WcfSystemUtility.ExecuteStatic(IMethodInstance methodInstance, ILobSystemInstance lobSystemInstance, Object[] args, IExecutionContext context)
at Microsoft.SharePoint.BusinessData.Runtime.DataClassRuntime.ExecuteInternalWithAuthNFailureRetry(ISystemUtility systemUtility, IMethodInstance methodInstanceToExecute, IMethod methodToExecute, ILobSystemInstance lobSystemInstance, ILobSystem lobSystem, IParameterCollection nonReturnParameters, Object[] overrideArgs)
at Microsoft.SharePoint.BusinessData.Runtime.DataClassRuntime.ExecuteInternal(IDataClass thisDataClass, ILobSystemInstance lobSystemInstance, ILobSystem lobSystem, IMethodInstance methodInstanceToExecute, IMethod methodToExecute, IParameterCollection nonReturnParameters, Object[]& overrideArgs)

I also received the following exception immediately after the above exception:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ServiceModel.QuotaExceededException: The size necessary to buffer the XML content exceeded the buffer quota. Server stack trace:

at System.ServiceModel.Channels.BufferedOutputStream.WriteCore(Byte[] buffer, Int32 offset, Int32 size)
at System.Xml.XmlStreamNodeWriter.FlushBuffer()
at System.Xml.XmlBinaryNodeWriter.FlushBuffer()
at System.Xml.XmlStreamNodeWriter.GetBuffer(Int32 count, Int32& offset)
at System.Xml.XmlBinaryNodeWriter.UnsafeWriteText(Char* chars, Int32 charCount)
at System.Xml.XmlBinaryNodeWriter.WriteText(Char[] chars, Int32 offset, Int32 count)
at System.Xml.XmlBaseWriter.WriteChars(Char[] chars, Int32 offset, Int32 count)
at System.Xml.XmlBinaryWriter.WriteTextNode(XmlDictionaryReader reader, Boolean attribute)
at System.Xml.XmlDictionaryWriter.WriteNode(XmlDictionaryReader reader, Boolean defattr)
at System.ServiceModel.Channels.ReceivedFault.CreateFault12Driver(XmlDictionaryReader reader, Int32 maxBufferSize, EnvelopeVersion version)
at System.ServiceModel.Channels.MessageFault.CreateFault(Message message, Int32 maxBufferSize)
at System.ServiceModel.Channels.SecurityChannelFactory`1.ClientSecurityChannel`1.TryGetSecurityFaultException(Message faultMessage, Exception& faultException)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.ProcessReply(Message reply, SecurityProtocolCorrelationState correlationState, TimeSpan timeout)
at System.ServiceModel.Channels.SecurityChannelFactory`1.SecurityRequestChannel.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Dispatcher.RequestChannelBinder.Request(Message message, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message) Exception rethrown
at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at BCSServiceProxy.IWXManageCustomerIn.FindCustomerByElements(FindCustomerByElementsRequest request)
at BCSServiceProxy.WXManageCustomerInClient.BCSServiceProxy.IWXManageCustomerIn.FindCustomerByElements(FindCustomerByElementsRequest request)
at BCSServiceProxy.WXManageCustomerInClient.FindCustomerByElements(BPCCustGetAll BPCCustGetAll) -
-- End of inner exception stack trace ---
at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Microsoft.SharePoint.BusinessData.SystemSpecific.Wcf.WcfSystemUtility.Execute(Object[] args)

With my experiences with SAP, buffer overflows normally indicate that a large Java stack trace is coming back from SAP and it overloads the WCF client buffer which is expecting a normal SOAP response.

To see the real exception, I tried to use Wireshark (http://www.wireshark.org/)- but our setup had (not by choice) Netweaver and SharePoint on the same box - and Wireshark cannot listen to localhost traffic.

As usual, the Fiddler Tool HTTP proxy (http://www.fiddler2.com/fiddler2/) came to the rescue- as it can actually listen to traffic occurring between applications on the same machine. When running Fiddler and listening to the https traffic, it popped up with an exception regarding certificate errors:

Session #24: The remote server (ausyd-a-sh1) presented a certificate that did not validate, due to RemoteCertificateChainErrors.

SUBJECT: CN=ausyd-a-sh1, OU=I0020310622, OU=SAP Web AS, O=SAP Trust Community, C=DE
ISSUER: CN=ausyd-a-sh1, OU=I0020310622, OU=SAP Web AS, O=SAP Trust Community, C=DE
EXPIRES: 1/01/2038 11:00:01 AM
I went to the server url in Internet Explorer and sure enough there was a certificate error occurring when going to the URL of the SAP server - at https://ausyd-a-sh1:8001/


Basically this exception was occurring because the SAP SSL certificate was not in the Trusted Root Certification Authorities store in Windows. To import it, I just opened mmc at a command prompt and added the certificates Snap-In (via File - Add/Remove Snap In... - for the Local Computer.

In Certificates (Local Computer), I went to Trusted Root Certification Authorities - Certificates and Imported the SAP Certificate in.

However, I kept getting the same exception. On closer inspection, the thumbprint of the SharePoint Security Token Service (STS) certificate was not the same as what had been imported into SAP - this was becuase SharePoint had been reinstalled - which changes the SharePoint STS certificate (i.e. SharePoint STS certificates are install specific).

I then received the following Exception - which was related to user mappings in SAP:

An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail


To resolve this last exception:
1) We made sure SharePoint:: was used to prefix the name in SAP (in the VUSEREXITID table) - this prefix would normally be added through the Duet Active Directory user import job (specific to Duet Enterprise) - but we don't have AD in our environment.
2) We then updated the STS certificate in the SAP Certificate store (using SAP transaction /nstrust)
3) Used the /nsaml2 transaction to update the certificate used there as well.

This resolved all our SAP to SharePoint communication issues.

DDK

Tuesday, 30 August 2011

FIX - SharePoint 2010 - Disabled New, Extend and Delete Buttons for Web Applications in Central Administration

My current client (a large NSW Government Department) has a test environment that doesn't have Active Directory and they required Duet Enterprise to be installed. After a basic install of SharePoint 2010 with SP1- in Windows Server 2008 R2, SQL 2008 R2, I was alarmed to find that I couldn't create new web applications via Central Administration. When I hovered over the buttons, it said that this functionality is disabled due to insufficient permissions:


I also couldn't add users to the farm administrators account - I would get an odd exception - "Local administrator privilege is required to update the Farm Administrators' group."

I tried rebooting, uninstalling, reinstalling to no avail - the same problem persisted.
There are many recommended fixes for this problem - most of which didn't work for me:
  1. Run in an alternative browser such as Google Chrome/Firefox - this didn't work for me.
  2. Ensure that the Application Pool account in IIS has the correct database permissions - this wasn't the problem in my situation as the user was a full local administrator and sysadmin on the database
  3. Ensure that you run IE as an administrator - this is already done by the default shortcut to the SharePoint Central Admin (I wasn't opening up SharePoint Central Admin from a seperately instantiated browser - so this also wasn't the problem)
  4. Ensure that UAC is turned off as this somehow interferes with the application of security to those controls. Most references to this indicated that this worked for Windows 7, but there was no reference to this working for Windows Server products.
To my surprise, number 4 was the one that worked for me. To do this (in Windows Server 2008+), you have to go to:
Control Panel - User Accounts - Turn User Account Control On or Off

After the reboot, the controls were suddenly re-enabled. This is not a recommended configuration - but this was purely for demonstration purposes rather than being a production-ready install (we would be using Active Directory for that anyway).

DDK






Friday, 26 August 2011

SQL Server 2008 R2 Master Data Services (MDS) IWorkflowTypeExtender - Implementation and Debugging

SQL 2008 R2 Master Data Services (MDS) has a basic plugin framework which allows you to handle events when business rule workflows are kicked off. You implement your custom plugins by:
1) Creating a class that implements IWorkflowTypeExtender. This is contained in the following assembly:
C:\Program Files\Microsoft SQL Server\Master Data Services\WebApplication\bin\Microsoft.MasterDataServices.Core.dll

2) Build and deploy the file to the bin directory (typically "C:\Program Files\Microsoft SQL Server\Master Data Services\WebApplication\bin") or a subdirectory of it if you use a PrivatePath (discussed below).
3) Modify the "Microsoft.MasterDataServices.Workflow.exe.config" file to point to your new assembly:

<configuration>
  <configsections>
    <section name="loggingConfiguration" requirepermission="true" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
    <sectiongroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <section name="Microsoft.MasterDataServices.Workflow.Properties.Settings" requirepermission="false" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
    </sectiongroup>
  </configsections>


4) Add another section to your config file that indicates what workflows to listen to (based on the value flag)

<applicationSettings>
    <Microsoft.MasterDataServices.Workflow.Properties.Settings>
      <setting name="ConnectionString" serializeAs="String">
        <value>Server=.;Database=MasterDataServices;Integrated Security=SSPI</value>
      </setting>
      <setting name="WorkflowTypeExtenders" serializeAs="String">
        <value>PAC=CompanyName.MasterDataWorkflow.WorkflowExtender, CompanyName.MasterDataWorkflow;OOB=Microsoft.MasterDataServices.Workflow.WorkflowTypeTest, Microsoft.MasterDataServices.Workflow, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91</value>
      </setting>
    </Microsoft.MasterDataServices.Workflow.Properties.Settings>
  </applicationSettings>
  <appSettings>

The plugin will allow you to obtain information about the calling workflow via the StartWorkflow(string workflowType, System.Xml.XmlElement dataElement) method that must be implemented as part of the interface of IWorkflowTypeExtender. You receive all the business context data via the data element parameter as an Xml snippet.

Once you have deployed this assembly and update the config files, the simplest way to debug inside Visual Studio is to go to your Project Properties, Debug Tab and Set the Startup Program to be
"C:\Program Files\Microsoft SQL Server\Master Data Services\WebApplication\bin\Microsoft.MasterDataServices.Workflow.exe".

After doing that, add a commandline parameter of "-console" which will give you a visual display of workflow plugins that have been loaded into the MDS Workflow Application Domain. It also allows you to print out to the console via Console.WriteLine() to assist with your development and debugging efforts.

Debug Setup

The MDS Workflow Console window:

As a best practice, use a PrivatePath in your Assembly Bindings in your Microsoft.MasterDataServices.Workflow.exe.config file. This will allow fusion to find your custom binaries in a subfolder rather than the binary root. Keeping all custom binaries in subfolders will help you to avoid file naming conflicts or bloating your root MDS workflow bin directory.


The simplest way to see what's going on is to just render the Xml from the workflow handler to the MDS workflow console window via an XmlTextWriter e.g.

public void StartWorkflow(string workflowType, System.Xml.XmlElement dataElement)
        {
            Console.WriteLine("workflow type: {0} ", workflowType);

            var writer = new XmlTextWriter(Console.Out);
            writer.Formatting = Formatting.Indented;
            dataElement.WriteTo(writer);

DDK