DevExpertise

Practical tips and tricks for all things .NET, SharePoint, Silverlight, InfoPath, and general application development.

Archive for the 'InfoPath' Category

Fixing the "Selected file was not found” Error When Adding an Attachment to an InfoPath Workflow Task Form

Posted by DevExpert on 1st July 2009

If you’re at all familiar with creating Visual Studio-authored workflows for SharePoint, you’re probably aware that you can also create custom task forms for tasks that have been assigned in that workflow.  The task forms can either be implemented as ASP.NET forms or InfoPath forms. My personal preference is InfoPath, provided the functionality that is needed can be accomplished using InfoPath.  By choosing InfoPath, much of the overhead required to allow SharePoint to use it as a task form is already done, whereas with ASP.NET it’s up to the developer to implement.

On a recent project, I had a need to create a workflow that at a certain step, prompted the user to upload a file attachment.  InfoPath was the obvious choice because they’re simple to create, simple to deploy, and they even support attachments.  After creating the form and the workflow I tried attaching a file to the InfoPath task form, and received the following error message:

 image

I searched around a little and found that this is a known issue, and there are a few ways to fix it.  This post suggests adding the following bit of JavaScript to the WrkTaskIP.aspx page, which is responsible for hosting the InfoPath task form:

<script type="text/javascript">
    aspnetForm.encoding = "multipart/form-data";
</script>

Another approach documented here suggests modifying the application.master master page file to add the necessary enctype form tag:

<form runat="server" onsubmit="return _spFormOnSubmitWrapper();" enctype="multipart/form-data">
  ...
</form

While both of these approaches work, I wasn’t completely happy with modifying out-of-the-box SharePoint files, which isn’t recommended anyways.  I took a slightly more involved, but much more reusable approach. Any customization you make to SharePoint really should be implemented as a Feature.  I’m not going to go into the nuts and bolts of what features are, but you can learn a little more about them from this previous post.  Before I explain my approach, here are your options for accomplishing this:

  1. Modify the out-of-the-box application.master master page file on the file system.  Not recommended.
  2. Modify the out-of-the-box WrkTaskIP.aspx page on the file system.  Not recommended.
  3. Create a new task content type, create a copy of WrkTaskIP.aspx and rename it, add the JavaScript to that page, associate the new ASPX page to your new content type, then point your workflow task forms to that new content type.  Not impossible, better than the above approaches, but still a lot of unnecessary work.
  4. Create a user control with the necessary script and deploy it as a delegate control feature.  Recommend, and the approach I describe in this post.

I decided to go with JavaScript, because I knew I could insert that into a page a lot easier than I could insert a form tag attribute.  Using an approach I’ve used before on previous projects, I decided to implement this using the delegate control pattern.  Take a look here and here first for a primer on delegate controls, but basically what they allow you to do is insert custom user or server controls into a place holder in the master page using a feature.  This is perfect, because you can add functionality to all site pages immediately without actually modifying a single page.

The first step is creating the user control that will be responsible for adding the encoding to the form.  I took a little different approach here too, and used jQuery to attach the encoding element to the forms. Obviously this implies that the jQuery library is being referenced already.  There are other solutions that make this a cinch to accomplish, such as this one by the great Jan Tielens.  In fact, this solution uses the exact same approach that I’m blogging about here.  My DevExpertise.TaskFormEncoding.ascx user control looks like this:

<%@ Control Language="C#" ClassName="DevExpertiseTaskFormEncoding" %>
<script type="text/javascript" >
    $(document).ready(function() {
        $("form").each(function(i) {
            this.encoding = "multipart/form-data";
        });
    });
</script>

Now that I have the user control created, I need to create a SharePoint feature to insert this into a delegate control placeholder.  Both the default.master page and application.master pages that SharePoint use by default have a delegate control named AdditionalPageHead, which is there specifically for this purpose – to add script, CSS references, etc. to the page’s HEAD section:

<SharePoint:DelegateControl runat="server" ControlId="AdditionalPageHead" AllowMultipleControls="true"/>

The following snippet is my feature.xml file, which defines the feature.  This file is responsible for specifying the feature ID, Title, Description, etc., plus any element manifests that our feature requires:

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
         Id="2D597698-5FBC-482a-BA0F-2E42DEA2E8E4"
         Title="DevExpertise InfoPath Workflow Task Form Encoding"
         Description="Adds the necessary form encoding to allow attachments
                      to be added to InfoPath workflow task forms."
         Scope="Site"
         Version="1.0.0.0"
         ImageUrl="DevExpertise\devexpertiseLogo.png"> 

  <ElementManifests>
    <ElementManifest Location="elements.xml" />
  </ElementManifests>
</Feature>

This feature is expecting an elements.xml manifest file, which is responsible for wiring up my custom user control to the AdditionalPageHead delegate control in the master page:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Control
    Id="AdditionalPageHead"
    ControlSrc="~/_controltemplates/DevExpertise/DevExpertise.TaskFormEncoding.ascx"
    Sequence="10"/>
</Elements>

The only thing left is packaging everything up and deploying it. I always package my features up into solution packages using WSPBuilder, since a solution package can be used to deploy other feature-dependant files to SharePoint’s 12 folder structure. My entire solution folder structure looks like this:

image

Create the solution package, deploy it to SharePoint, and you will have a site collection feature ready for activation:

image

That’s it!  Now you can successfully add attachments:

image

You may be asking yourself why I went through all that trouble just to add a couple lines of JavaScript to my pages, when I could’ve just opened application.master and thrown it in there.  Well, it’s not a recommended best practice to modify system files.  If an update or service pack is installed that overwrites these files, then all your modifications are gone.  In addition, a change like that would have to be made to all web front-end servers in your farm.  This approach is extremely reusable – I can take this solution package and activate it in any site collection in any farm and I have the necessary functionality.

I’m feeling generous today and decided to include the solution package for your downloading pleasure.  As always, it comes as-is and without warranty:

Tags: , , , , ,
Posted in InfoPath, JQuery, JavaScript, SharePoint, Workflow | 2 Comments »

Creating a Wizard-Style Browser-Enabled InfoPath Form

Posted by DevExpert on 16th February 2009

InfoPath is a great tool when you need to build simple data-entry forms.  Although the tool is clearly limited when it comes to creating a rich user interface with complex controls, there are things you can do to enhance the experience if you’re creative.

One of the ways various applications collect data is through the familiar wizard-style interface.  A wizard can be great if you need to collect a lot of information from user, but want to break it down into manageable chunks.  At first glance, InfoPath does not support such a thing, as there isn’t a fancy wizard control like there is in ASP.NET.  Luckily InfoPath allows us to create views, which provide a completely different display for the fields defined in the form.  With a little code and finesse, we can successfully implement a wizard-style browser-enabled InfoPath form.  The end result looks like the following:

image

The first thing to do is define the fields that are required in the form.  For the sake of this post, I built a very simple New User Wizard with a few fields, but in the real world a wizard is better suited for collecting a lot of data:

image

Now that the fields are defined, a view needs to be created for each “page” of the wizard.  In this case, I created 2 views in addition to the default view, and also renamed the default view to page1:

image

On the first page, I laid out the controls for the first page, and also included a Cancel button, Back button, and Next button.  I really don’t need a Back button for the first page, but i kept it in there as a placeholder and just disabled it by adding a conditional format.  In addition to the fields that the user needs to fill out, we have to perform our own data validation.  The reason is the validation doesn’t fire until the form is submitted, and we need to perform incremental validation just to ensure the first page’s fields are filled out correctly.  I’ll get to the code required to do this shortly, but for now I just added the errorMessage field, set the fill to transparent, removed the border, and disabled it:

image

Next, I created the second page of the wizard by cutting & pasting the first page and adding the appropriate fields:

image

Finally, I created the third page of the wizard, which for this scenario is just a summary page of previously filled-in data:

image

Now that the form UI is set up, we can start adding the rules and code to make it behave like a wizard.  Since I like my code nice and pretty, I renamed all of my buttons to btnCancel1, btnBack1, btnNext1, btnCancel2, etc.  For each cancel button, I added a rule to just close the form.

For the back button, we don’t need to validate the fields, so the code required to jump back to the previous page is straightforward.  To switch views I used the SwitchView() method of the form’s ViewInfoCollection:

public void btnBack2_Clicked(object sender, ClickedEventArgs e)
{
    this.ViewInfos.SwitchView("page1");
}

public void btnBack3_Clicked(object sender, ClickedEventArgs e)
{
    this.ViewInfos.SwitchView("page2");
}

 

Next I needed to implement the “Next” functionality, but before we switch the view to the next page, the fields on the current page need to be validated.  For this, I added the errorMessage field to each of the pages, and if the validation fails, I set the value of that field to the error message.  To do the validation and to set the field, I created two wrapper methods: GetFieldValue and SetFieldValue:

private string GetFieldValue(string fieldName)
{
    string xpath = string.Format("/my:myFields/my:{0}", fieldName);
    return MainDataSource.CreateNavigator().SelectSingleNode(xpath, NamespaceManager).Value;
}

private void SetFieldValue(string fieldName, string value)
{
    string xpath = string.Format("/my:myFields/my:{0}", fieldName);
    MainDataSource.CreateNavigator().SelectSingleNode(xpath, NamespaceManager).SetValue(value);
}


Now for the Next buttons, I can verify data has been entered and if not, set the errorMessage field value:

public void btnNext1_Clicked(object sender, ClickedEventArgs e)
{
    if (!string.IsNullOrEmpty(GetFieldValue("firstName")) &&
        !string.IsNullOrEmpty(GetFieldValue("lastName")) &&
        !string.IsNullOrEmpty(GetFieldValue("emailAddress")))
    {

        SetFieldValue("errorMessage", string.Empty);
        this.ViewInfos.SwitchView("page2");
    }
    else
    {
        SetFieldValue("errorMessage", "Please fill out all required information");
    }
}

public void btnNext2_Clicked(object sender, ClickedEventArgs e)
{
    if (!string.IsNullOrEmpty(GetFieldValue("workPhone")) &&
        !string.IsNullOrEmpty(GetFieldValue("cellPhone")) &&
        !string.IsNullOrEmpty(GetFieldValue("address")))
    {

        SetFieldValue("errorMessage", string.Empty);
        this.ViewInfos.SwitchView("page3");
    }
    else
    {
        SetFieldValue("errorMessage", "Please fill out all required information");
    }
}


The end result is something like this when an error occurs:

image


If all the data has been entered successfully, the last page is eventually visible:

image

 

Pretty slick, huh?

Tags: , ,
Posted in .NET, InfoPath, SharePoint | 6 Comments »

Why Isn’t InfoPath Retrieving All SharePoint List Items?

Posted by DevExpert on 5th February 2009

InfoPath and SharePoint make a great team when it comes to creating simple electronic forms.  It’s relatively simple to create a robust data-entry form, and is easy to submit an InfoPath form to a form library.  It’s even simple to pull in data that is stored in a SharePoint list.  However, what you may not realize is if a data connection is configured to retrieve SharePoint list items, it only retrieves the items from the first page of the default view.  What does that mean?  Let’s say you have a SharePoint list that contains 500 items, and your default view (the view that is loaded whenever you visit the list) is set to only show 100 items at a time (like it is by default).  When InfoPath executes its query to retrieve the list items, it will only retrieve 100 items.  More specifically, it will only retrieve the first 100 items.  So, if you happen to change the default view’s criteria or sort order, then InfoPath will be pulling in a different set of data! Don’t believe me? Let’s take a look…

To begin, I created a custom list in SharePoint named Widgets, and added 100 items to it.  Next, I created a new view named Sample View in the list, and set that as the list’s default view:
image

Next, I set the Item Limit to only 7 items per page:
image

After clicking OK and saving the view, whenever I access my list I’m presenting with the first 7 items in the list:
image

Now that I have a data source, I create my InfoPath form and set up a data connection to retrieve data from the Widgets list.  I added the results of the data source to a repeating table, and whenever I run my form, I am only presented with the first 7 items (told ya!):
image

Just to make sure this is the actual behavior and I’m not just hallucinating after a long day, I modified the view and changed the sort order to sort by Title in descending order, and changed the item limit to display 12 items at a time:
image image

When I open the form again and the data connection is executed, it retrieves the first 12 items after the new sort has been applied!:
image

One would think that there would be some type of configuration option in the data connection wizard that asks to retrieve all items, or the results from a specific view, but there isn’t.  If you do need to retrieve all list items, it’s imperative that your list’s default view displays them all, or consider a different data retrieval method such as the built-in or custom web services.

There’s even a KB article that says this behavior is by design: http://support.microsoft.com/kb/892954

Tags: ,
Posted in InfoPath, SharePoint | 6 Comments »