InfoPath 2010 – Contact Selector required validation

Today, I put validation on the now built-in Contact Selector in my InfoPath form. The requirement is to enable users to fill in no or more internal employees. If a new employee is added, a validation check has to occur to ensure the user is known.

I start with adding the following components to the form:

1. create the control, in this case the Contact Selector. To enable users to fill in more than 1 employee, I place the node as a child under a repeating group
2. create a section next to it with something like “Please fill in user”
3. create a rule on the section to show when the value of the Contact Selector is empty

Now, this is the trick; if you create the rule, do not use the “DisplayName” attribuut of the contact selector, but the “AccountId” attribute to check if it is blank or not. If a user fills in an invalid account name, it cannot be resolved to the AccountId, so this will stay empty, causing the section to show. Implement the same rule on the “Proceed” button.

Windows SharePoint Services 3.0 Service Pack 2 installation issue

Recently, I tested the installation of the new Service Pack 2 for both WSS 3.0 and MOSS. This test was performed on a box with Sql Server 2005, SharePoint Services 2.0, 3.0, Portal Server 2003 and MOSS. It was used as a in-place upgrade test server, where the upgraded portal is still available as a read-only portal.

After the installation of the WSS sp2, you need to run the Configuration Wizard. This wizard stopped with an error after a while however, with a vague message that it could not find some file. After analysing the logs and upgrade reports, I noticed that the wizard tried to contact the old administration site. This url was found in a registry key.

Resolution:

Apparently, somehow, if the WSS services are still running, the wizard takes the url to the old administration site which causes the error. The resolution was to completely remove WSS 2.0 or to stop the WSS 2.0 services.

MOSS Migration issue: cannot create sub sites despite having full control permission

Nice one. The migration went smoothly without any major issues. Via the default approach (test migration, UAT migration, live migration) the new portal went live. After a week, the owner reported a strange behaviour; only user which are members of the “site collection administrators” are able to create sub sites. Other users, although being assigned “Full Control” permissions in the portal, are immediately redirected to the access denied page.

I cracked open my migration test box and started to do some more tests:

– if a completely new site collection was created, hosted by the same web application as the migrated portal, everything worked fine

– if the user was added to the web application policy with “read” rights, he/she can create sub sites

– as i knew that the migrated portal had custom permission levels in the SPS2003, I focussed my effort on this. I checked all permission level in the site collection and the web application level

Resolution:

Investigating the support site of Microsoft (first place to go, believe me, start here) did not give back an exact description; but I did see hot fixes with similar behaviour. These issues were all solved with post SP1 hot fixes. So, I installed the Service Pack 2 for the WSS 3.0 and MOSS, and this solved the issue.

Follow up:

It gets weirder.. My customer went on installing Service Pack 2 on their UAT (good practice), and confirmed the issue was solved on the UAT environment. However, suddenly, the issue was also resolved on the production! Cool! Is MOSS self repairing..? Anyway, I am investigating this at the moment, I think it has to do with the database server, which is shared by both UAT- and production environment. I cannot find information, but I will install the SP2 on my VM to see which components are actually installed. I hope it installs some Sql Server updates as well, which might explain why the issue suddenly disappeared.

InfoPath Submit programmatically with Forms Server – spsite.MakeFullUrl

For a recent project, I needed code to submit an InfoPath form to a forms library. Since this library could have embedded folders, I had to revert to use code for this. Unfortunately, if you use "Submit" it will save only to the root folder or a pre defined Url. Hope this one is solved with SharePoint 2010….!

If you create an InfoPath form via forms server, you will see a querystring attribute called "SaveLocation". This will be a string in the format"/div/hrcontent/formstest/testfolder/myinfopathform.xml". If you edit an InfoPath form you will see a querystring called "XmlLocation" in a similar format.

You can use these strings to build up the location where to submit your form; you just have to convert them to absolute Url’s in your code. Since we use forms server, we are able to get the SPContext; so our code is aware of the SharePoint site where it works within. This is handy, as we can use the relative paths of the described query strings to convert them into absolute Url’s. With this, we do not need to encode any hard coded paths anymore!

The following code is quite straightforward:

InfoPath form Design: create a submit connection like you are used to in you InfoPath designer screen. For convenience, call it "main submit". Later, we will alter this submit connection via code.

Loading event: get the query strings and construct the Url to save to. Since we use forms server, you need to use FormState variables; you can see these as kind of session variables. So, as long as the form is open, you will be able to get to the location.

Submit event: when you submit the form, all you do is replace the pre defined submit location in your form with the location you saved in your FormState variable.

And finally, the code! Note here that this is quickly assembled and not really "clean and lean" enough. If someone has cleaned up the code and wants to share, please let me know.

 

using Microsoft.Office.InfoPath;
using System;
using System.Xml;
using System.Xml.XPath;
using Microsoft.SharePoint;

namespace MyInfoPathForm

{
    public partial class FormCode
    {
        private object _strUri
        {
            get { return FormState["_strUri"]; }
            set { FormState["_strUri"] = value; }
        }
        private object _strLocation
        {
            get { return FormState["_strLocation"]; }
            set { FormState["_strLocation"] = value; }
        }
        private object _strFoldername
        {
            get { return FormState["_strFoldername"]; }
            set { FormState["_strFoldername"] = value; }
        }

        // NOTE: The following procedure is required by Microsoft Office InfoPath.
        // It can be modified using Microsoft Office InfoPath.
        public void InternalStartup()
        {
            EventManager.FormEvents.Loading += new LoadingEventHandler(FormEvents_Loading);
            EventManager.FormEvents.Submit += new SubmitEventHandler(FormEvents_Submit);
        }

        public void FormEvents_Loading(object sender, LoadingEventArgs e)
        {
            // Get the Uri (or SaveLocation in a browser form) of where
            // the form was opened.
            // See if the form was opened in the browser
            Boolean OpenedInBrowser = Application.Environment.IsBrowser;
            // If so, we will get the "SaveLocation" from the InputParameters
            if (OpenedInBrowser)
            {
                if(e.InputParameters.ContainsKey("XmlLocation"))
                {
                    _strUri = SPContext.Current.Site.MakeFullUrl(e.InputParameters["XmlLocation"].ToString());

                }
                else if(e.InputParameters.ContainsKey("SaveLocation"))
                {
                    //new form
                    _strUri = e.InputParameters["SaveLocation"].ToString();

                }
            }
            else
            {
                //If it was opened in the client, we will get the Uri
                _strUri = this.Template.Uri.ToString();
            }
            //store the location and foldername
            string strUri = _strUri.ToString();
            string strPath = "";
            if (OpenedInBrowser)
            {
                strPath = strUri.Substring(0, strUri.LastIndexOf("/"));

            }
            else
            {
                strPath = strUri;
            }
            string strLoc = strPath.Substring(0, strPath.LastIndexOf("/"));
            string strFolder = strPath.Substring(strPath.LastIndexOf("/") +1);
            _strLocation = strLoc;
            _strFoldername = strFolder;

        }

        public void FormEvents_Submit(object sender, SubmitEventArgs e)
        {
             // Create a Navigator object for the main DOM
            XPathNavigator xnDoc = this.MainDataSource.CreateNavigator();
            // Get a reference to the submit data connection
            FileSubmitConnection fc = (FileSubmitConnection)this.DataConnections["Main submit"];
            // Modify the URL we want to submit
            fc.FolderUrl = _strLocation.ToString() + "/" + _strFoldername.ToString();

            // Execute the submit connection
            try
            {
                fc.Execute();
                e.CancelableArgs.Cancel = false;
            }
            catch
            {
                e.CancelableArgs.Cancel = true;
            }
        }
    }
}

SharePoint Portal Server 2003, Visual Studio 2005, 64 bit and the error 80040154

Today, i ran into a unknown problem to me while I was implementing a migration cleanup tool on the production server. In the test environment, the tool performed well, but (rule of thumb..?) while running it on the production server it went out with a strange error:

  • Unhandled exception has occurred in your application. … Retrieving the COM class factory for component with CLsID {BDEADEBD-C265-11D0-BCED-00A0c90AB50F} failed due to the following error: 80040154

The tool is created with Visual Studio 2005 and targets the .net Framework 2.0. It has one reference to the SharePoint.dll (2003).

After some troubleshooting, it appeared that this server was a Windows Server 2003 with 64bit O/s. That was the main difference between the test- and production server, so I thought it was worth looking into this. Turned out that you have to compile your tool with the "x86" switch. This ensures that if the IL runs your code, it will run in a 32 bit process. Since SharePoint 2003 is only available as 32 bit software, this solved the problem.

You can set this switch by opening your project properties, tab "Build", and open the drop down list next to "Platform Target". Recompile, and that’s it!

Using the Content Query Web Part without activating the publishing feature

In my experience, a lot of customers are overwhelmed with the OOTB features in MOSS. They have to cope with new concepts like XSLT, web parts, master pages, layout pages, lists, libraries, user profiles, etc. Even in the case of a simple team site, the starting MOSS customer needs a lot of guidance.

In virtually every project so far I needed the Content Query web part (CQW) at some point to roll up information from various places within the solution. The CQW is part of the "publishing feature" in MOSS and is activated automatically if you turn the "Publishing feature" on. So, what if you do want to use the CQW, but want to avoid the "Publishing Feature" with all its (unused) bells and whistles?

I reasoned that the CQW is already installed in your MOSS environment, so why not use it anyway. It only needs some extra steps before you can use it:

Export the CQW from a publishing site to the file system 
1. create a site collection where you activate the "Publishing" feature
2. add a CQW to one of your pages
3. now, export this CQW to the file system by selecting "Edit – Export". Store it somewhere easy to find

Import the CQW to the site collection
1. open up your site collection settings in the site where you want to use the CQW
2. browse to the "Web Part" gallery
3. click "Upload", and browse to your saved CQW on the file system
4. after clicking "Ok" you will be asked to fill in some metadata properties

Now you have your CQW added to your site collection! We are not finished yet however, because the CQW relies on some XSL to render its returned results. In this sample, I use the default location for these files in the Style library. This library is created after activating the publishing feature. Alternatively, you can use your own location by specifying this if you edit the CQW.

Copy the CQW style sheets in your site collection
(you can do this on several ways, I prefer SharePoint Designer because of its "drag and drop" functionality)

1. open up SharePoint Designer and your site collection where you have exported the CQW from
2, navigate to the Style Library, right click on this folder and select "Copy"
3. open up a second SharePoint Designer instance, open the site collection where you imported the CQW
4. right click on the root in this site and select "paste"
5. now, in this case, we only need the folder "XSL Style Sheets" within the Style Library. You can delete all the other folders in the Style Library

In this case, i leave all the xsl files in this folder. The CQW only uses the files "ContentQueryMain.xsl, Header.xsl, ItemStyle.xsl, and the LevelStyle.xsl. the others are used by other web parts like the "Summary Link web part". You can use these web parts by importing them into your site, similar to the procedure described above.

Excellent BDC tutorial

In my quest to gain knowledge about creating BDC connections to LOB systems, i stumbled upon this very, very good post from Sahil Malik (Mvp), which guides you through the xml files needed for defining connections (as he refers to as "xml goo"..).

The article is divided in several steps, from just getting a simple list to adding actions to you definition file. Also, information about synchronizing with the user profile database, tools to use, etc. Great work, saved me hours of reading msdn documentation!!

Rebuild your spfileversion history on a document

For a migration project from sps2001 to MOSS we had a requirement to keep the minor/major versioning intact. As we had to use sps2003 as a migration staging environment, the minor versions are lost and every version will be stored as a major version in MOSS. This was not acceptable, since major versions are visible to visitors of the site, while they are actual minor versions.

So, what to do? Fortunately, we also migrated the original source address in sps2001 as metadata column, so we are able to get the original version number without having to browse through the 2001 environment. It’s all there in the column "Source Address", in the format "http://localhost/ws/shadow/documents/testversiondoc(1.1).doc".

With a lot of help of this article on SharePoint blogs I decided to rebuild the version history. So, for example, if the version history of a document looks like this:

Version number in MOSS Original Version number in 2001 (found in column "Source Address")
1.0 0.1
2.0 0.2
3.0 1.0
4.0 1.1

then all the minor versions have to be reverted back to a minor version, which will avoid readers to see the minor versions. There is no requirement to retain the original version number. So, we created a script to copy the versions as new versions and, based on the original file version number, only publish those which have a major version.

Few drawbacks here:

  • original modified- and creation dates are not retained (I tried to figure this out, but since we use the publish method, the file will be published on the moment you call this method. Even the updateoverwriteversion does not seem to work) – tips appreciated!
  • original version numbers are not retained, only the minor- and major versions will be in the history as minor- and major

The code looks like this:

==============================================================================

private SPFile AddFile(string urlFile, byte[] bytefile, Hashtable properties, bool overwrite, SPFileVersion origver)
       {
           SPFile add = rootfolder.Files.Add(urlFile, bytefile, properties, true);
           string SourceAddress = (string)origver.Properties["Source Address"];
           PublishVersion(int.Parse(GetVersion(SourceAddress, true)), add);
           return add;
       }
       #endregion
private void RebuildVersionHistory(SPFile file)
       {
           ArrayList myIds=new ArrayList();          //to store ID’s of versions in

           if (file.Versions.Count != 0)
           {
               //foreach (SPFileVersion newver in file.Versions)
               for (int i = 0; i < file.Versions.Count; i++)
               {
                   SPFileVersion oldver = file.Versions[i];

                   myIds.Add(oldver.ID);
                   AddFile(file.Url, oldver.OpenBinary(), oldver.Properties, true, oldver);
               }

               //add the latest version as current, then check if current version should be published
               AddFile(file.Url, file.OpenBinary(), file.Properties, true, file.Versions[0]);

               //and delete the old versions..
               for (int i = 0; i < myIds.Count; i++)
               {
                   SPFileVersion filetodelete = file.Versions.GetVersionFromID(int.Parse(myIds[i].ToString()));
                   filetodelete.Delete();
               }
               //and delete the first one in version history (this is the original published one
               file.Versions[0].Delete();
           }
           else
           {
               //just check this one version if it should be published or not
               string SourceAddress = (string)file.Properties["Source Address"];
               PublishVersion(int.Parse(GetVersion(SourceAddress, true)), file);
           }
       }

public void PublishVersion(int num, SPFile setver)
       {
           int baseNum = 512;
           decimal d = num / baseNum;
           int i = (int)Math.Floor(d) * 512;

           //major publish (eg 1.0, 2.0, 3.0)
           if (num == i)
           {
               setver.Publish("");
           }
       }  
private string GetVersion(string UrlIn, bool as512)
       {
           //format:blblalbalbal (1.0).xxx . We filter on combination ). an take everything before that till the ( sign
           //Example: http://localhost/ws/shadow/documents/folder 1/documentx(1.0).docx –> 1.0
           string ret="";
           if(UrlIn.IndexOf(").") >0)
           {
               ret = UrlIn.Substring(UrlIn.LastIndexOf(").") – 3, 3);
               if(as512)
               {
                   //ok. now the fun stuff. We now have something like 1.0, or 2.1, 5.0 etc. However, MOSS uses a different system
                   //for storing this:
                   //URL number “1” = version “0.1”
                   //URL number “2” = version “0.2”
                   //URL number “3” = version “0.3”
                   //URL number “512” = version “1.0”
                   //URL number “1024” = version “2.0”
                   //URL number “1025” = version “2.1”
                   //URL number “2048” = version “3.0”
                   //URL number “2049” = version “3.1”
                   //etc. So, logic: (first digit X 512) + second digit = number to parse back
                   int temp1=int.Parse(ret.Substring(0,1)); //first digit
                   int temp2=int.Parse(ret.Substring(2,1)); //second digit
                   int result = temp1 * 512;
                   result = result + temp2;
                   ret = result.ToString();
               }
           }
           return ret;
       }

==============================================================================

Basically, these are the steps taken:

– get all versions, extract the original file versions and add them to a sortedlist. This list will ensure the ordering is being done on the version number

– then, copy each version as a new version and publish this version only if it is a major version originally.

– then, add the original latest version as the current one. Note here, this is not in the version history, this is the current version (see the article on SharePoint blogs for more details)

– delete all the old versions which have been copied already (we stored them in the list myIds)

– delete the original file (this was the original current version before we started this process).

Result: a document with a version history with the current major/minor versioning. Bear in mind the file numbers will not be the same as in the 2001 environment (if someone found a way, please let me know!!), but at least you will have the correct minor/major versioning in your history.

“Mailto” in People Search Core Results list

One of my customer’s wanted to be able to email all the users in a people search results list. Another requirement was to use as much OOTB functionality as possible. So, i decided to extend the existing "People Search Core Results web part" with an XSL template which would do the job.

I decided to go for the way Microsoft designed the web part. I like the links on top of the results list, where a user can choose several options, depending on the results. For example, an RSS feed is offered. It would be more than logical to place my "Email to" link here, isn’t it? This is the result:


Results list: notice the "Email users in this list" link

To embed this functionality, modify your People Search Core Results list with your favourite XSL editor, in my case SharePoint Designer. Copy and paste in the following template:

<!– This template is called for each result and will create a mailto link of each displayed user (if any email present)–>
<xsl:template name="ShowMailToEmailLink">
        <xsl:variable name="ListEmail">
                <xsl:variable name="Rows" select="/All_Results/Result"/>
                <xsl:for-each select="$Rows">
                     <xsl:if test=’string-length(workemail) &gt; 0′>
                         <xsl:value-of select="workemail"/>;
                    </xsl:if>
                </xsl:for-each>
        </xsl:variable>
        <xsl:if test=’string-length($ListEmail) &gt; 0′>
            |
            <a>
                <xsl:attribute name="href">mailTo:<xsl:value-of select="$ListEmail"/>
                </xsl:attribute>
                Email users in this list
            </a>
        </xsl:if>
</xsl:template>

As you can see, it is quite simple; a variable is constructed with the email addresses (if present) concatenated as a list with the ";" sign as separator. Then, a check is performed to see if there is something in the variable, if so, a "mailto" hyperlink is created.

Next question is where to call this template from. As said, I want to show this link in the options on top of the results list. These options are defined in the "dvt_1.body" template. I added the following line between the "</xsl:if>" and "</div>" tag to call the template: 

<xsl:call-template name="ShowMailToEmailLink"/>

Save the modified xsl, save the page (ignore the errors) and, if needed, publish the page. Since the results of the web part are used, there are some notes here:

  • only results which have a valid email address will be used;
  • only the results on the current page will be used. So, if you configured the web part to use 8 results per page, only these 8 results will be used in the link.
  • this implies that you are only able to email the first 50 users, since this is the maximum configurable amount of results shown on one page.

So, have fun!

Debugging User Controls in your SharePoint Web Part

Although a lot of people don’t think it is possible, today i found that it is possible. After building my user control, i discovered that hosting it in the SmartPart did not do the trick. Under the hood, the user control calls an assembly to add a user to Active Directory. Although we could run the code if we used a a web part; the code would throw a generic exception once it was executed by the user control.

And that made sense; I am happy that I was not able to use a .acsx and it .cs file for this kind of work. Would not be very secure.. But I needed to find a way to do this. Microsoft uses the Control templates folder in the 12 hive to store its user controls. Hmm.. that folder might be configured to have some more rights and permissions to run code.. Also, I saw that third party components (for example K2) used this folder to store their user controls in. So it was quickly decided to follow their example.

The problem now was that the SmartPart will not let you choose another folder then the "[virtual directory]/Usercontrols" one (unless you want to alter their code of course). No worries; a custom web part which loads the user control dynamically was quickly build. And… yes! It worked!

By accident I also noticed that i was suddenly able to debug my user control! Apparently, the ControlTemplates folder has some additional configuration so Visual Studio picks it up automatically. Just attach the web part to the w3p processes, and it will automatically load the user control if you create an instance of it in your web part code.