Just wrapped up a project developing a custom metadata editor to plugin to the Colligo Contributor for SharePoint client.
If you aren’t familiar with Colligo, it’s a desktop client application/outlook add-in/explorer file manager, that enables users to save documents or emails directly to SharePoint, and be prompted for metadata at the time of saving.
Colligo reads the content type of the target lists/libraries, and generates a default form very similar to what SharePoint does with the default EditForm.aspx ListForm web part. If you don’t like the default form, or need to customize the metadata entry experience (for example custom validation, pulling from external data sources, inter-dependent controls), you can develop a custom editor and show your own Windows Forms-based form to the user.
The default Colligo Editor form, shown here when adding a new Announcement.
How to Start
To start developing a custom editor, begin by installing Colligo Contributor, and obtaining your product and sdk keys from Colligo.
Create a new Visual Studio Class Library project, and add a reference to the Colligo dlls in the GAC.
Add the necessary using statements:
1 |
<span style="color: #0000ff">using</span> Colligo; |
1 |
<span style="color: #0000ff">using</span> Colligo.Properties; |
1 |
<span style="color: #0000ff">using</span> Colligo.Util; |
1 |
<span style="color: #0000ff">using</span> Colligo.WML.MetaData; |
Change your class to inherit from the ICustomEditor class:
1 |
<span style="color: #0000ff">namespace</span> MyNamespace |
1 |
{ |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> MyCustomEditor : ICustomEditor |
1 |
{ |
1 |
  |
1 |
} |
1 |
} |
Implement the Interface methods:
1 |
<span style="color: #0000ff">namespace</span> MyNamespace |
1 |
{ |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> MyCustomEditor : ICustomEditor |
1 |
{ |
1 |
  |
1 |
<span style="color: #008000">// SDK Key</span> |
1 |
<span style="color: #0000ff">private</span> <span style="color: #0000ff">const</span> <span style="color: #0000ff">string</span> SDK_KEY = <span style="color: #006080">"XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"</span>; |
1 |
  |
1 |
<span style="color: #cc6633">#region</span> ICustomEditor Members |
1 |
  |
1 |
<span style="color: #0000ff">public</span> <span style="color: #0000ff">string</span> GetSdkKey() |
1 |
{ |
1 |
<span style="color: #0000ff">return</span> SDK_KEY; |
1 |
} |
1 |
  |
1 |
<span style="color: #0000ff">public</span> EditorResult ShowEditor(IEditContext context) |
1 |
{ |
1 |
<span style="color: #008000">// Show your form here</span> |
1 |
  |
1 |
} |
1 |
  |
1 |
<span style="color: #0000ff">public</span> ResolveConflictResult ShowResolveConflictDialog(IResolveConflictContext context) |
1 |
{ |
1 |
<span style="color: #0000ff">return</span> ResolveConflictResult.UseDefaultResolveDialog; |
1 |
} |
1 |
  |
1 |
<span style="color: #cc6633">#endregion</span> |
1 |
  |
1 |
} |
1 |
} |
In the ShowEditor method, this is where you can create an instance of a Windows Form, and call the ShowDialog method on it. You can also pre-validate and avoid showing your form for situations that are not appropriate.
1 |
<span style="color: #0000ff">public</span> EditorResult ShowEditor(IEditContext context) |
1 |
{ |
1 |
  |
1 |
<span style="color: #008000">// Check if this is something we care about</span> |
1 |
<span style="color: #0000ff">if</span> (!context.List.IsDocumentLibrary) |
1 |
{ |
1 |
<span style="color: #0000ff">return</span> EditorResult.UseDefaultEditor; |
1 |
} |
1 |
<span style="color: #0000ff">if</span> (!<span style="color: #0000ff">string</span>.Equals(context.List.Name, <span style="color: #006080">"Private Documents"</span>, StringComparison.CurrentCultureIgnoreCase) && |
1 |
!<span style="color: #0000ff">string</span>.Equals(context.List.Name, <span style="color: #006080">"Public Documents"</span>, StringComparison.CurrentCultureIgnoreCase)) |
1 |
{ |
1 |
<span style="color: #0000ff">return</span> EditorResult.UseDefaultEditor; |
1 |
} |
1 |
  |
1 |
<span style="color: #008000">// Only show for Create/Edit (use the default Colligo form for Viewing)</span> |
1 |
<span style="color: #0000ff">if</span> (context.EditorMode == EditorMode.Create || context.EditorMode == EditorMode.Edit) |
1 |
{ |
1 |
<span style="color: #0000ff">using</span> (myCustomWindowsForm myForm = <span style="color: #0000ff">new</span> myCustomWindowsForm(context)) |
1 |
{ |
1 |
DialogResult createResult = DialogResult.None; |
1 |
  |
1 |
createResult = myForm.ShowDialog(context.Parent); |
1 |
  |
1 |
<span style="color: #0000ff">switch</span> (createResult) |
1 |
{ |
1 |
<span style="color: #0000ff">case</span> DialogResult.Cancel: |
1 |
<span style="color: #0000ff">return</span> EditorResult.Cancel; |
1 |
<span style="color: #0000ff">case</span> DialogResult.OK: |
1 |
<span style="color: #0000ff">return</span> EditorResult.OK; |
1 |
<span style="color: #0000ff">case</span> DialogResult.No: |
1 |
<span style="color: #0000ff">return</span> EditorResult.UseDefaultEditor; |
1 |
} |
1 |
} |
1 |
} |
1 |
<span style="color: #0000ff">else</span> |
1 |
{ |
1 |
<span style="color: #008000">// View - Show the default editor</span> |
1 |
<span style="color: #0000ff">return</span> EditorResult.UseDefaultEditor; |
1 |
} |
1 |
  |
1 |
<span style="color: #008000">// Fall through</span> |
1 |
<span style="color: #0000ff">return</span> EditorResult.OK; |
1 |
  |
1 |
} |
Notice I am passing in the IEditContext instance into my form’s constructor. The IEditContext object has all the goodies in it, and will give you access to the list, the web, the content types, list item metadata, etc.).
What to Consider
Some basic things to consider are:
- Which scenarios will your editor cover? View, Create, Edit?
- Is this only for documents, or list items, or both?
- Will you support content types? A list/library can be configured with multiple content types, which you will need to create a UI for selecting/changing if you decide to support that.
- How will you handle multiple files? Will you enable setting different data for each item, or will you force all items to use the same entered metadata? Think carefully about the “Name” field for documents in document libraries. You want to make sure that the same name is not used for multiple documents.
- Will you allow changing folders (for document libraries with folders enabled)? If so, you’ll need to build this UI.
There are a number of ways/methods that may kick off the display of your form, and you need to consider each of these:
- User selects New > Item from within Colligo Contributor
- This is similar to the New > [Content Type] menu directly in SharePoint. You can expect that EditorContext.ContentType and EditorContext.ListItems[0].ContentType will not be null.
- User selects Upload from within Colligo Contributor
- This is similar to Upload Document from within SharePoint. EditorContext.ContentType and EditorContext.ListItems[0].ContentType may be null.
- User drags/drops a single file onto Colligo Contributor
- EditorContext.ContentType and EditorContext.ListItems[0].ContentType may be null.
- User drags/drops multiple files onto Colligo Contributor
- EditorContext.ContentType and EditorContext.ListItems[0].ContentType may be null.
- User performs a single File > Save As from within an application
- EditorContext.ContentType and EditorContext.ListItems[0].ContentType may be null.
- User drags/drops multiple files onto the Colligo area in Windows Explorer
- EditorContext.ContentType and EditorContext.ListItems[0].ContentType may be null.
- User selects Edit Properties from within Colligo Contributor
- Need to grab existing values and prepopulate your controls. EditorContext.ContentType and EditorContext.ListItems[0].ContentType will not be null.
Some Gotchas
Following are some gotchas to be aware of:
Check for Null
Don’t make any assumptions about any of the items in the IEditContext object. ContentTypes may be null, Fields may not exist or be null, other relevant ILists in the Web may not be synchronized locally, and will be null, etc., etc., etc. Check everything for null first, and test everything that returns a collection/array for Count and Length > 0. If there are fields/lists/lookup column values that your form expects, check for the existence of these things right away, and exit if you don’t find them.
Parent Windows
If your form is called via the Save As dialog from an application (or via the FileManager), the ParentWindow handle may not be valid, so be careful with the ShowDialog(IWin32Window) overload, which threw a System.ComponentModel.Win32Exception for me. I had to wrap my ShowDialog(context.Parent) in a try>catch, and then try it without any parameters (ShowDialog() ).
Custom Field Types
If you are using any custom field types, be really careful to make sure and store the data in the proper format. My customer was using a couple of custom field types found on CodePlex, and one of them extended the Lookup field, and it took me a few traces with Fiddler to figure out that it wanted its values in lookup column format (“52;#ItemName”);
Site Lookup Columns
If you have any lookup columns that are defined in higher-level sites, and you are using those site columns in lists located in subsites, then Colligo will not be able to see the lookup values. In order to implement these scenarios, you will have to force your users to synchronize the higher level sites as well as the subsites.