May 27 2010

Creating a Custom Metadata Editor Plugin for Colligo for SharePoint

Category: SharePoint, TechnologyAdam Toth @ 2:07 pm

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.

Colligo default editor form
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:

using Colligo;

using Colligo.Properties;

using Colligo.Util;

using Colligo.WML.MetaData;

Change your class to inherit from the ICustomEditor class:

namespace MyNamespace

{

    public class MyCustomEditor : ICustomEditor

    {

 

    }

}

Implement the Interface methods:

namespace MyNamespace

{

    public class MyCustomEditor : ICustomEditor

    {

 

        // SDK Key

        private const string SDK_KEY = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX";

 

        #region ICustomEditor Members

 

        public string GetSdkKey()

        {

            return SDK_KEY;

        }

 

        public EditorResult ShowEditor(IEditContext context)

        {

            // Show your form here

 

        }

 

        public ResolveConflictResult ShowResolveConflictDialog(IResolveConflictContext context)

        {

            return ResolveConflictResult.UseDefaultResolveDialog;

        }

 

        #endregion

 

    }

}

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.

public EditorResult ShowEditor(IEditContext context)

{

 

    // Check if this is something we care about

    if (!context.List.IsDocumentLibrary)

    {

        return EditorResult.UseDefaultEditor;

    }

    if (!string.Equals(context.List.Name, "Private Documents", StringComparison.CurrentCultureIgnoreCase) &&

        !string.Equals(context.List.Name, "Public Documents", StringComparison.CurrentCultureIgnoreCase))

    {

        return EditorResult.UseDefaultEditor;

    }

 

    // Only show for Create/Edit (use the default Colligo form for Viewing)

    if (context.EditorMode == EditorMode.Create || context.EditorMode == EditorMode.Edit)

    {

        using (myCustomWindowsForm myForm = new myCustomWindowsForm(context))

        {

            DialogResult createResult = DialogResult.None;

 

            createResult = myForm.ShowDialog(context.Parent);

 

            switch (createResult)

            {

                case DialogResult.Cancel:

                    return EditorResult.Cancel;

                case DialogResult.OK:

                    return EditorResult.OK;

                case DialogResult.No:

                    return EditorResult.UseDefaultEditor;

            }

        }

    }

    else

    {

        // View - Show the default editor

        return EditorResult.UseDefaultEditor;

    }

 

    // Fall through

    return EditorResult.OK;

 

}

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.

Bookmark and Share

Tags: ,


May 26 2010

Filtering XML output of SPListItemCollection using Linq to XML

Category: SharePoint, TechnologyAdam Toth @ 1:57 pm

I’ve been writing a slurry of code to return list item XML so that it can be parsed and consumed by AJAX components on the client side. Since Lists.asmx does not work in anonymous access scenarios, I’ve been creating many proxy .ashx Http Handlers installed in the LAYOUTS directory, to query via the object model and return the XML of the list items, using the .Xml property of a SPListItemCollection.

I had one particular scenario where I couldn’t pre-filter the items in the collection with an SPQuery object (too complicated a query for CAML), so I needed a way to filter the SPListItemCollection afterwards using C#, before returning the XML.

The SPListItemCollection offers the .Delete() and .DeleteItemById() methods, however they will commit the deletion back to SharePoint. I wanted to simply remove some items from the in-memory collection, not delete the items from the server. There was no way that I could see to remove items from an SPListItemCollection in memory only.

I looked into sub-classing or extending the SPListItemCollection class, but there is no public constructor in the base class, and no way to get at the logic behind the .Xml property without nasty reflection.

I ultimately ended up resorting to using Linq to XML to delete the rows directly from the returned XML, and then adjusted the ItemCount accordingly.

Here’s the code:

// Get the list items from a list

SPListItemCollection items = myList.GetItems(mySPQuery);


// Get the xml of the list items

itemXml = surveyItems.Xml;


XNamespace z = "#RowsetSchema";

XDocument xdoc = XDocument.Parse(itemXml);


IEnumerable<XElement> rows = xdoc.Root.Descendants(z + "row");

List<XElement> rowsToDelete = new List<XElement>();


foreach (XElement row in rows)

{

    // Here is where you can test for a condition, and decide to remove a row

    if (row.Attribute("ows_Title").Value.Contains("Some string to filter on"))

    {

        rowsToDelete.Add(row);

    }

}


while (rowsToDelete.Count > 0)

{

    // Update the parent row count

    int rowCount = Convert.ToInt32(rowsToDelete[0].Parent.Attribute("ItemCount").Value);

    rowsToDelete[0].Parent.Attribute("ItemCount").Value = (rowCount - 1).ToString();

    rowsToDelete[0].Remove();

    rowsToDelete.RemoveAt(0);

}


// Get the newly filtered xml

itemXml = xdoc.ToString()

Bookmark and Share

Tags: , , , ,


Apr 04 2010

xPollinate Preview of HelloTxt Support

Category: TechnologyAdam Toth @ 8:48 pm

I have a new version of xPollinate waiting in the wings, with new support for HelloTxt. With this new feature, you won’t have to choose between Ping.fm or HelloTxt – you can use one, the other, or both (just try not to spam the hell out of your friends with duplicate posts!).

I’ve submitted my application for approval at HelloTxt, and hopefully it’ll be a quick approval process. I can’t release the version for testing yet, because it only works with my own user key right now until it gets reviewed and approved (sort of a chicken and egg thing if you ask me).

In the meantime, I thought I’d post a quick screen shot of the new tab and highlight some features:

New xPollinate tab for HelloTxt

After entering your API key, xPollinate will pull in your Tags, and all of your defined services (networks). You can either choose a tag to post to (which will select only those networks in the tag), or you can choose All Networks and then individually select the networks to post to.

Notice that the account name of each service is posted next to the service name. This is because HelloTxt support multiple accounts with the same service (i.e. more than one twitter account).

Account names 

Just like Ping.FM, you can choose to post either as a status/microblog update using a template, or post as a blog entry.

Blog or status options

I’m excited to get this release out, and hope the application gets accepted soon.

Bookmark and Share

Tags: , , , ,


Mar 25 2010

Best Indoor Flood Lamp for Recessed Lights – GE Reveal

Category: Home Repair, LifeAdam Toth @ 3:35 pm

I installed eight 5″ recessed can lights in my living room about five years ago. I haven’t yet had to replace a single one of the bulbs I first installed. The GE Reveal R30 flood lamps have lasted a long time, and provided a really pleasant, pure white light.

GE BR30 Reveal Light Bulb

In my new kitchen can lights, a cheap contractor pack of Sylvania 65W flood lights didn’t even last one year before all 13 bulbs burnt out. I’ve replaced them all one-by-one with the GE Reveal bulbs, and none have burnt out, over one year later.

I’m sure the performance has a lot to do with having dimmers. I’ll bet it’s a lot more gentle on the filaments when you turn on a light with the dimmer down to begin with. Even still, these Reveal bulbs are great.

Bookmark and Share

Tags: , ,


Feb 22 2010

MP3Tag Rename Script

Category: UncategorizedAdam Toth @ 11:55 am

So I don’t forget it, and lose it when reimaging my machine, I’m posting my mp3tag file rename script. It takes the audio files that I have tagged, prefixes with a padded track number, formats as 001 – album – title.ext and shortens the name if it starts to get too long and nears the windows 256 file/path character length limitations.

$num(%track%,3) – $validate($replace(%album% – $iflonger(%_folderpath%nnn – %album% – %title%.%_extension%,256,$left(%title%,$sub(230,$len(%_folderpath%nnn – %album% – ))),%title%),.,-),-)

Bookmark and Share


Dec 16 2009

My PDC Portrait

Category: Technology, Touch Screen ComputingAdam Toth @ 3:05 pm

Here is my portrait done by an artist with brushes on a touch screen Windows 7 computer at PDC 09:

mypdcportrait

Bookmark and Share

Tags: , , ,


Nov 18 2009

PDC Day 1 – Lap Around Windows Azure Notes

Category: ASP.NET, Azure, WCF, WFAdam Toth @ 9:02 am

    Session #1:

    Windows Azure Platform:

  1. Windows Azure
  2. SQL Azure
  3. AppFabric

    …plus developer tools and "Dallas"

    Azure does "Compute" "Management" and "Storage"

    SQL Azure is a relational DB as a service.

    Demo is of a tickmaster-type application that uses Azure.

    TicketDirect Architecture:

    "Compute" using Web and Worker roles to process tickets

    Using the service bus to communicate with on-premise services.

    Storage of data in SQL Azure

    Printing of tickets is offloaded to the site (i.e. venue).

    Site receives a message via service bus to print the ticket for pickup at role call

Another Demo, sample app, of basic CRUD web app.

CRUD is standard ASP.NET SqlDataSource control with SQL commands, that works with SQL Express but runs in the cloud

Demo’d using Trace.Write which writes out to the development console logger and can also write to the Azure Storage logging subsystem

Migrated to SQL Azure by just changing the connection string

Storage (BLOBs, Tables, and Queues)

NEW – Add ability to mount Azure storage as NTFS drive

Coming soon – Ability to manage VMs with admin privileges

Create snapshots of your VMs

When up-scaling, Azure will deploy your app on top of your custom VM image

SQL Azure:

Only pay for what you use

Do not worry about disaster recovery

Change a connection string an have effortless switch to SQL Azure

Sync framework to sync SQL DBs in the cloud with on-premise data

Service Bus:

Securely connect apps (on-premise with cloud)

Tunneling technology

Services bus is a middle man for communication between

Access Control:

Provides outsourcing of claims-based RESTful services

Integrates with ADFS v2

Bookmark and Share

Tags: ,


Nov 18 2009

PDC Day 1 KeyNote Notes

Category: ASP.NET, SharePoint, Silverlight, Technology, WCF, WF, WPF, WinFormsAdam Toth @ 8:53 am

    Windows Azure Improvements since last year:

  1. Support PHP, CGI, Apache, other frameworks
  2. Expose very low-level programming efforts (not just .NET).
    Example was a C++ app with pointers exposed as an Azure service
  3. Identity framework (support passing tokens from federated locations i.e. onsite AD instance)

    SQL Azure Improvements since last year:

  1. Not just RESTful consumption of services anymore
  2. Works with standard TDS based tools (SQL management studio)

    Microsoft has a vision of "Three screens and a Cloud".

    The screens include:

  1. Mobile devices
  2. Desktop computers
  3. Internet connected TVs

    They can all be united by data and services in the cloud.

Public Data in the Cloud (Codename "Dallas").

Repository for public data sets that can be consumed in any number of ways (and easily by Azure)

Accessed through Microsoft PinPoint

Sign up for a CTP key

Some data includes NASA mars photos, GIS data, AP News articles

PinPoint:

Centralized marketplace for partner providers, Azure ISVs and implementers, and gateway to "Dallas" public Data.

"System Center" will plugin to Azure to monitor your Azure instances, check to meet SLAs, and enable you to scale up the Azure instances directly.

2010 will include ability to have the Azure cloud be able to establish a network connection to on-premise resources (i.e. self hosted SQL Server)

WordPress is moving to Windows Azure

Bookmark and Share

Tags: , , ,


Oct 20 2009

.NET Plugin DLL Hell (aka assembly binding nightmares)

Category: Technology, WinFormsAdam Toth @ 6:08 pm

I’ve worked on a few .NET WinForms applications that utilized a “plugin” model. You’ve probably seen similar apps out there, where a main application searches a directory for DLLs, and then tries to load them into the AppDomain, using some code like below:

Assembly plugin = System.Reflection.Assembly.LoadFrom(pluginFileName);

object pluginClass = Activator.CreateInstance(plugin.GetType("Plugin.PluginClass"));

 

This kind of architecture is pretty standard, and is one of the few ways to accomplish this plugin model in .NET (other than some cutting-edge things like MEF). It works for the most part, but has some obvious limitations, such as:

  • Once a DLL is loaded in this manner, it cannot be unloaded from memory (unless you mess around with AppDomains for each plugin)
  • If your plugins want to talk to one another, they pretty much have to go through a lot of plumbing code in the main app

Where it really starts to have problems is when your plugin DLL needs to reference other DLLs used by the main application, or other plugin DLLs, and you find yourself in .NET Plugin DLL Hell.

Here is the crux of the problem:

When you add a reference to a DLL via Visual Studio, the framework will always try to load an exact matching version of that assembly, and will fail if it can’t find it. So, good luck if that DLL you depend on ever gets upgraded to a new version.

You can usually control this with a standalone application, because when you upgrade the app, you upgrade all the dependencies at the same time. In a plugin model, you can’t control when other dependencies are upgraded, and so your plugin will simply fail to load when this happens.

There is no way in the .NET framework to tell it to use a particular version of a DLL or any newer version it finds.

Here’s a scenario to demonstrate this:

  • You have a WinForms application, that loads plugin DLLs when the app starts.
  • The main application uses a third-party control library.
  • You write a plugin that also needs the controls, so you add a reference to the version that the main app uses.
  • At some point, someone working on the main app upgrades it to use a newer controls DLL, recompiles the app, and distributes it along with the newer controls DLL.
  • Since no work was actually done on your plugin, no one has recompiled it against the newer controls DLL.
  • When the upgraded application starts and loads your plugin, your plugin goes BOOM!

Another scenario:

  • Your plugin references another plugin
  • The other plugin gets upgraded without your plugin getting recompiled
  • Your plugin now goes BOOM!

When I say “BOOM!”, I mean you’ll probably see something like this from the Fusion log:

"System.IO.FileNotFoundException: Could not load file or assembly ‘YourDependentAssemblyNameHere’ or one of its dependencies. The system cannot find the file specified.” 

I am having a similar problem with my Windows LiveWriter plugin, xPollinate. Every time Microsoft releases a new version of LiveWriter, all of its DLLs that I reference have new version numbers, and so my plugin will fail to load unless I recompile my plugin against the latest versions of the DLLs.

Well, so what are our options to deal with this?

  • You can simply not add any references in your plugin project via Visual Studio, and just use reflection for everything (yuck). This is probably a little easier now with the dynamic keyword in C#, but not much.
  • You can try to get the main application to put in assembly binding redirects in its config file for any common DLLs that might be referenced by plugins.

That’s all I can see at this point. Here’s to hoping a future version of the framework will make this easier for us.

Bookmark and Share

Tags: , , , , ,


Oct 12 2009

1.0.0.3 Release of xPollinate

Category: TechnologyAdam Toth @ 3:56 pm

I just released a new version of xPollinate, 1.0.0.3.

This is just a maintenance release to make the plugin work with Windows LiveWriter version 14.0.8089.726.

I am currently working on supporting categories for the next release, so stay tuned.

Bookmark and Share

Tags: ,


Next Page »