Jun 06 2008

Schema Validation Errors with InfoPath Document Information Panels

Category: InfoPath, SharePointAdam Toth @ 9:03 pm

I ran into a situation with some custom InfoPath Document Information Panels where I was getting schema validation errors like this one because of a Business Data Column I added to a document library:

“…{guid}ColumnName is unexpected according to content model of parent element ‘documentManagement’.”

You might run into this in the following situation:

  1. You create the DIP on a Site Content Type.
  2. You configure a document library to use your Site Content Type.
  3. You add a custom column to the Document Library (and subsequently to the List Content Type (the local instance of your site content type that is applied to the list)).

In this situation, your Document Information Panel was created without the knowledge of this extra column, and does not have a field in its data source schema for this column. What I believe is happening is that when attempting to view the DIP, SharePoint is passing all the columns on the document library to the InfoPath form, and the form is not expecting this extra column, so it chokes (very lame, Microsoft).

To get around this:

  1. Edit the DIP on the List Content Type (List Settings, click content type name, Document Information Panel Settings, Edit this Template link)
  2. Once the form loads in InfoPath, choose Tools > Convert Main Data Source…
  3. Enter the absolute url to your document library (e.g. http://yoursitecollection/subsite/your doc library name), and click Next.
  4. Finish the wizard and view your Data Source. Expand the nodes and you should see the extra list column appear now.

This is also a great way to update the DIP on your List Content Type, especially if you have locally overridden the Optional/Required/Hidden settings on columns from the parent Site Content Type.

This workaround kind of defeats the whole point of setting the Panel at the Site Content Type level, but that’s the only way I could get it to work.

Tags: , ,


Jun 06 2008

Workarounds for ItemAdding/ItemAdded Event Handlers

Category: SharePointAdam Toth @ 3:25 pm

I had the need to set Business Data and other column values in the ItemAdding event handler. I had to use ItemAdding, because I needed the changes committed so that the values I set would be visible on the Edit Properties screen immediately after uploading a document. Using the synchronous handler guaranteed that the values would be set.

I ran into the following issues with ItemAdding/ItemAdded event handlers:

  • Setting AfterProperties for some columns was not working using the InternalName of the field.
  • For Microsoft Office document file types, any data that I set in ItemAdding event via AfterProperties was lost after the first time the item was edited.

According to other people’s experience, when using the AfterProperties in ItemAdding event, you must use the Internal Name of the field, not the display name.

properties.AfterProperties[list.Fields["Display Name"].InternalName] = somevalue;

When I did this, however, my experience was exactly the opposite. It was only by using the Display Name of the field that I could actually get any of the data to stick. I’m not sure if it was my particular mix of SP1, Site Content types, 64bit server, and the use of a Business Data column on the document library that was making it not work, but I simply had to use the display name, or else none of the data I set would stick (my column names had a mix of spaces and no spaces, and interestingly the column names with no spaces worked either way).

This whole architecture of hashtables of properties and trying to match up values seems to me to be pretty fragile, and I’m not sure why Microsoft went this way. It also seemed to create my second problem, where data I did set disappeared.

Once I got things working with the Display Name of the field, I then noticed that I had a problem with the data disappearing after I would edit the properties of the document. But this was only for Microsoft Office document types, things were fine for image files, text files, xml files. In debugging, I checked the values at ItemAdding, ItemAdded, ItemUpdating, and ItemUpdated. Everything looked as it should through ItemUpdating, but somewhere in between ItemUpdating and ItemUpdated, the data was lost, and my values were set to empty strings. It seems there is something strange with the parser for Office file types.

To get around this, I had to set the values again in the ItemAdded event and Update the ListItem.

I would have simply switched to using ItemAdded exclusively, since that seemed to retain the data in all cases, but my code was executing a call to the Business Data Catalog, and so was a little slow. This would cause errors from SharePoint when a user would change data in the Edit Properties screen after upload, because it would complain that the file was recently updated. Generally a problem I would expect from an asynchronous handler.

To solve this, I do the heavy lifting code in ItemAdding, and then simply do a resetting of the value in ItemAdded to ensure that it sticks.

properties.ListItem["DisplayName"] = properties.AfterProperties["DisplayName"];properties.ListItem.SystemUpdate();

I still don’t feel comfortable using ItemAdded for this, but it seems to be fast enough that I don’t have any problems.

Tags:


Jun 02 2008

Using a BDC Item/Entity Picker Control in Custom SharePoint Application Pages

Category: SharePointAdam Toth @ 11:41 pm

I recently had the need to create a custom SharePoint application page (living in_layouts directory) that needed to display the BDC entity picker control, so that a user filling out this form could select a BDC entity instance, exactly like a user would in a business data column defined on a list. It took a while to figure it out, here are the steps to get it working:

Step 1 – Dissecting the ItemPicker control

The control that MOSS uses to render the BDC entity picker is called ItemPicker. You can add it to your aspx page by adding the following reference and code:

<%@ Register TagPrefix="Portal" Namespace="Microsoft.SharePoint.Portal.WebControls" Assembly="Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

 

<Portal:ItemPicker class="ms-input" ID="YourEntityPickerID" runat="server" />

Like the People Picker control (PeopleEditor), you have properties that you can set, such as AllowEmpty, MultiSelect, NoMatchesText, etc. However, there is one more property that you have to set, ExtendedData, that cannot be easily set in the aspx markup, so I create a handler in the markup for the Init event, and then set all the properties in code behind:

<Portal:ItemPicker class="ms-input" OnInit="YourEntityPickerID_Init" ID="YourEntityPickerID" runat="server" />

 

Step 2 – Setting ExtendedData

In order for the control to work properly, you have to set the ExtendedData property, using the ItemPickerExtendedData Class. This needs to be done via code, so be sure to create a reference to your control first, and handle the Init event of the control:

using Portal = Microsoft.SharePoint.Portal.WebControls;

protected Portal.ItemPicker YourEntityPickerID;

protected void YourEntityPickerID_Init(object sender, EventArgs e)

{    

    // Set extended data    

    YourEntityPickerID.ExtendedData = GetExtendedData(        

         Settings.Default.LOBInstanceName,

         Settings.Default.EntityName,

         Settings.Default.EntityDisplayNameColumn);

    // Set other properties

    YourEntityPickerID.AllowTypeIn = false;

    YourEntityPickerID.AllowEmpty = false;

    YourEntityPickerID.AutoPostBack = false;

    YourEntityPickerID.MultiSelect = false;

}

I’ve broken it out into a little helper method that takes in three parameters, the LOB Instance Name (name of your BDC LOB application instance), Entity name (the name of your Entity, e.g. "Customer"), and the entity display name column (the Name attribute in the TypeDescriptor of the column that acts as the display name column for your entity).

using Microsoft.Office.Server;

using Microsoft.Office.Server.ApplicationRegistry;

using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;

using Microsoft.Office.Server.ApplicationRegistry.Runtime;

private Portal.ItemPickerExtendedData GetExtendedData(string lobInstanceName, string entityName, string titleFieldName)

{

    // Create a new ExtendedData object

    Portal.ItemPickerExtendedData data = new Portal.ItemPickerExtendedData();

    // Get the LOB Instance

    LobSystemInstance lob = ApplicationRegistry.GetLobSystemInstanceByName(lobInstanceName);

    data.SystemInstanceId = lob.Id;

    // Get the entity

    Entity entity = lob.GetEntities()[entityName];

    data.EntityId = entity.Id;

    // Set the primary column id (the id of the "Title" field)

    FieldCollection fields = entity.GetSpecificFinderView().Fields;

    List<uint> secondaryColumnIds = new List<uint>();

    foreach (Field field in fields)

    {

        if (string.Equals(field.Name, titleFieldName, StringComparison.OrdinalIgnoreCase))

        {

            data.PrimaryColumnId = field.TypeDescriptor.Id;

        }

        else

        {

            secondaryColumnIds.Add(field.TypeDescriptor.Id);

        }

    }

    data.SecondaryColumnsIds = secondaryColumnIds.ToArray();

    return data;

}

The important things happening here are:

  1. Setting the SystemInstanceId (this marries the control to a particular BDC application).
  2. Setting the EntityId (this marries the control to a particular entity).
  3. Setting the PrimaryColumnId (points the control to the TypeDescriptor that acts as the Identity column)
  4. Setting the SecondaryColumnIds (points the control to the TypeDescriptors for all the other columns you need to bring in).

Step 3 – Consuming the Data

To consume the data, perform some validation, and then try to get a PickerEntity object out of the Entities property of the control:

YourEntityPickerId.Validate();

if (YourEntityPickerId.Entities.Count <= 0)

{

    SPUtility.TransferToErrorPage("You must pick an Entity.");

}

PickerEntity bdcEntity = (PickerEntity)YourEntityPickerId.Entities[0];

if (bdcEntity == null || !bdcEntity.IsResolved || bdcEntity.EntityData == null)

{

    SPUtility.TransferToErrorPage("You must pick an Entity.");

}

Once you have a PickerEntity, you can get the ID of the entity instance via the Key property, the display name via the DisplayText property, and also get all the other column data via the EntityData property.

Note that the .Key property is the encoded ID of the entity instance (you can use EntityInstanceIdEncoder to encode/decode this ID). Note also that the EntityData comes back as a HashTable of TypeDescriptor ID/value pairs, so you can’t index into this with a friendly column name. You’ll have to traverse the Entity and its fields, get the TypeDescriptor ID of the column you are interested in, and then use that to index into the hash table. That can be a pain, so it’s almost easier to simply grab the .Key, decode it, and then call FindSpecific() on your Entity.

Tags: ,


Jun 02 2008

Recipe for cleaning moldy walls

Category: Home RepairAdam Toth @ 7:19 am

I got this recipe for a solution to cleaning moldy walls, like my concrete block garage walls currently are.

  • 1 5 gallon bucket
  • 4 gallons hot water
  • 1 cup TSP
  • 1 cup bleach
  • 1 cup pinesol

Mix and then scrub the walls. Then they’ll be ready for paint.