Sunday, 13 May 2012

SharePoint 2010: Create Custom Document Set Content Type using VS 2010


Every product evolves and so as SharePoint. In SharePoint 2010, the latest SharePoint version, one of the few remarkable enhancements added to Content Management is Document Set Content type.

One of the problems people facing in MOSS 2007 was there was no provision to group the different documents related to single entity (work product) and perform a collective operation on them like executing some workflows etc. In SharePoint 2010, Document Set content type is introduced to address such business needs.

Document Set content type is a special kind of folder which can store multiple documents. It brings following advantages over the normal folders.

Document Set Vs Folder content type:
1.       When document set is clicked it redirects user to a unique welcome page associated with the document set. Welcome page can be customized in the sense it allows adding new web parts, displaying welcome page fields (metadata) etc apart from displaying the documents within the document set. Normal folders when clicked just display documents within them .
2.       Ability to have Shared Metadata
Documents within the document set can share the metadata between them. Such shared fields can be displayed in welcome page. This is not possible using folders.
3.       Ability to have default documents
Document set can have default documents which will be automatically added to document set when a new document set is created. Folders do not possess such feature.
4.       Versioning:
Apart from the contained documents, Document Set have its own versioning capability.
5.       Workflows
Workflows are richly supported by Document Set. Few new OOB workflow actions are added in SharePoint Designer to support Document Set content type. Folder content type lacks many workflow capabilities and needs some workaround to be implemented. Have a look at http://www.codeproject.com/Articles/30452/SharePoint-Workflows-for-Folder-Content-Type
  
Limitations:

Note:
Strangely, I got an error while creating the document set with localhost in url. Everything worked seamlessly when I used the Default Zone url instead. No intension to troubleshoot it. If you are getting same error, you know the fix now.

Aim:
Custom Document set content type can be created using Server object model or in declarative manner (element.xml). We will see how to create it using declarative manner step by step in this post. As Document set very different than normal content types, creating it through VS 2020 in declarative fashion  is also very different than that of normal content type.

We will create a custom Loan Proposal content type inheriting from the Document Set content type. This custom Loan Proposal content type will have custom welcome page and one default document.

Detailed Steps:
1.       Ensure Document Set site collection feature is activated.
2.       Create a DocumentSetContentTypeDemo VS 2010 project using Empty SharePoint Project template.
3.       Add a web part to the project. Give the name of the web part as LoanRequestorImageWebPart as shown in figure 1.

Figure 1 : Create LoanRequestorImageWebPart

We will add this web part on the welcome page of the Loan Request content type so that it would display the photo of the requestor.

4.       Copy the below code into the web part so that LoanRequestorImageWebPart.cs will look like this.

using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;

namespace DocumentSetContentTypeDemo.LoanRequestorImageWebPart
{
    [ToolboxItemAttribute(false)]
    public class LoanRequestorImageWebPart : WebPart
    {
        const string NO_IMAGE_AVAILABLE = "/_layouts/Images/DocumentSetContentTypeDemo/NoImageUploaded.jpg";
        protected override void CreateChildControls()
        {
            if (this.Page.Request["ID"] != null)
            {
                int docSetId = Convert.ToInt32(this.Page.Request["ID"].ToString());
                SPList currentList = SPContext.Current.Web.Lists[new Guid(this.Page.Request["List"].ToString())];
                SPFolder folder = currentList.GetItemById(docSetId).Folder;
                string docSetRootFolder = this.Page.Request["RootFolder"].ToString();
                if (currentList != null)
                {
                    SPQuery qry = new SPQuery();
                    qry.Query = @"<Where>
                                     <Eq>
                                         <FieldRef Name='ContentType' />
                                         <Value Type='Computed'>Image</Value>
                                    </Eq>
                                 </Where>";
                    qry.Folder = folder;
                    SPListItemCollection listItems = currentList.GetItems(qry);
                    if (listItems.Count > 0)
                    {
                        SPListItem imageItem = listItems[0];
                        string name = imageItem["Name"].ToString();
                        string imageUrl = docSetRootFolder +"/" + name;
                        this.Controls.Add(new LiteralControl(string.Format("<img src='{0}' alt='{1}' height='150' width='140' style='padding:8px'/>", imageUrl, name)));
                    }
                    else
                    {
                        this.Controls.Add(new LiteralControl(string.Format("<img src='{0}' height='150' width='140' style='padding:8px'/>", NO_IMAGE_AVAILABLE)));
                    }
                }
            }
            else
            {
                this.Controls.Add(new LiteralControl(string.Format("<img src='{0}' height='150' width='140' style='padding:8px'/>", NO_IMAGE_AVAILABLE)));
            }
        }
    }
}


5.       Add an Image mapped folder to the project. Add the following image to the mapped folder.

Figure 2: No photo image
This image will be used when there is no loan requestor photo uploaded.

6.       Deploy the DocumentSetContentTypeDemo project.
7.       Create Following site Columns through browser

Site Column
Type
Required
Description
Group
Any other details
Loan Amount
Currency
Yes
Enter the loan amount required.
SharePoint 2010 Custom Development
Currency Format : Rs. (India)
Loan Reason
Single line of text
Yes
Enter the reason for which loan is required.
SharePoint 2010 Custom Development

Loan Type
Choice
Yes
Select the loan type.
SharePoint 2010 Custom Development
Choices:
Educational Loan
Personal Loan
Furniture Loan
Home Loan
Age
Number
Yes

SharePoint 2010 Custom Development

Contact Number
Single line of text
Yes

SharePoint 2010 Custom Development

Current Salary
Number
Yes

SharePoint 2010 Custom Development

Job Experience
Number
Yes
No. of years
SharePoint 2010 Custom Development

Occupation
Single line of text
Yes

SharePoint 2010 Custom Development

Sex
Choice
Yes

SharePoint 2010 Custom Development
Choices:
Male
Female


8.       Create following content types through browser
a.       Loan Application Form

Figure 3 : Loan Application Form content type

b.      Requestor Credit Details

Figure 4 : Requestor Credit Document content type

c.       Loan Proposal
Create a Loan application form document as shown in figure 5. We will use this document as Default document for Loan Proposal document set content type.

Figure 5 : Loan Application Form content

Then create a Loan Proposal content type as shown in figure 6.

Figure 6 : Loan Proposal Content type

Click on Document Set settings link shown in Red in figure 6 and make the configurations as shown in below figures.

Allow Content Types:

Figure 7 : Allowed Content Types

Default Content:
Upload the Loan Application form document created as default document using Loan Application Form content type. Once Loan Proposal document set is created, Loan Application Form will be added to the document set automatically. This then can be downloaded and sent to Loan Requestor for filling his/her details.

Figure 8 : Default Document

Shared Fields:

Figure 9 : Shared Fields

Welcome Page Columns:

Figure 10 : Welcome page columns

Welcome Page:
Click on Customize the Welcome Page link. It will redirect you to OOB Document set welcome page. Remove the Image web part and add LoanRequestorImageWebPart web part on welcome page.

9.       Save the site as site template as DocumentSetSiteTemplate using the Save site as template link present in Site Settings page.
10.   Download the DocumentSetSiteTemplate.wsp in local physical file system.
11.   In DocumentSetContentTypeDemo VS 2010 solution, add new project using Import SharePoint Solution Package template as shown below in figure 11.

Figure 11 : Import SharePoint Solution Package project

12.   In SharePoint Customization Wizard, select the DocumentSetSiteTemplate.wsp in Specify the new project source step and click next.
13.   In Select items to import step, select following items and click Finish button. Wizard will show Selection Dependacy Warining. Click on No button.

Content Types:
Loan Application Form
Requestor Credit Details
Loan Proposal

Fields:
Loan Amount
Loan Reason
Loan Type
Age
Contact Number
Current Salary
Job Experience
Occupation
Sex

Modules:
_ctsLoan Proposal_

14.   Copy Modules, Content Types and Fields folder into DocumentSetContentTypeDemo project. Your project will look like this.

Figure 12 : DocumentSetContentTypeDemo project

15.   If you open the Loan Proposal content type, you will see it quite different than normal content types. It will have information about Allowed Content types, shared fields, welcome page fields, Default Documents etc.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   <ContentType ID="0x0120D52000442F5196B2EA0C489F01F37985515E5B" Name="Loan Proposal" Group="SharePoint 2010 Custom Development" Overwrite="TRUE" ProgId="SharePoint.DocumentSet" PushDownChangedResourceFilesOnly="TRUE" xmlns="http://schemas.microsoft.com/sharepoint/">
      <Folder TargetName="_cts/Loan Proposal" />
      <FieldRefs>
          <FieldRef ID="{8553196d-ec8d-4564-9861-3dbe931050c8}" Name="FileLeafRef" Required="TRUE" Hidden="FALSE" />
          <FieldRef ID="{475c2610-c157-4b91-9e2d-6855031b3538}" Name="FullName" />
          <FieldRef ID="{cbb92da4-fd46-4c7d-af6c-3128c2a5576e}" Name="Description" ShowInNewForm="TRUE" ShowInEditForm="TRUE" />
          <FieldRef ID="{2defb7a6-aca0-43bc-8d73-cbd07e5b7503}" Name="Loan_x0020_Amount" />
          <FieldRef ID="{413b4744-f709-4952-8864-fd38e60bd754}" Name="Loan_x0020_Reason" />
          <FieldRef ID="{7cf8011a-9d47-44f4-a49e-f72d6b038161}" Name="Loan_x0020_Type" />
          <FieldRef ID="{b824e17e-a1b3-426e-aecf-f0184d900485}" Name="ItemChildCount" />
          <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" Required="FALSE" Hidden="TRUE" />
          <FieldRef ID="{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" Name="ContentType" />
          <FieldRef ID="{960ff01f-2b6d-4f1b-9c3f-e19ad8927341}" Name="FolderChildCount" />
      </FieldRefs>
      <XmlDocuments>
          <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/welcomepageview">
              <WelcomePageView xmlns="http://schemas.microsoft.com/office/documentsets/welcomepageview" LastModified="1/1/1 0:00:01 AM" />
          </XmlDocument>
          <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/events">
              <spe:Receivers xmlns:spe="http://schemas.microsoft.com/sharepoint/events">
                   <Receiver>
                        <Name>DocumentSet ItemUpdated</Name>
                        <Synchronization>Synchronous</Synchronization>
                        <Type>10002</Type>
                        <SequenceNumber>100</SequenceNumber>
                        <Assembly>Microsoft.Office.DocumentManagement, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
                        <Class>Microsoft.Office.DocumentManagement.DocumentSets.DocumentSetEventReceiver</Class>
                        <Data />
                        <Filter />
                   </Receiver>
                   <Receiver>
                        <Name>DocumentSet ItemAdded</Name>
                        <Synchronization>Synchronous</Synchronization>
                        <Type>10001</Type>
                        <SequenceNumber>100</SequenceNumber>
                        <Assembly>Microsoft.Office.DocumentManagement, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
                        <Class>Microsoft.Office.DocumentManagement.DocumentSets.DocumentSetItemsEventReceiver</Class>
                        <Data />
                        <Filter />
                    </Receiver>
              </spe:Receivers>
          </XmlDocument>
          <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/allowedcontenttypes">
              <act:AllowedContentTypes xmlns:act="http://schemas.microsoft.com/office/documentsets/allowedcontenttypes" LastModified="05/12/2012 13:23:22">
                    <AllowedContentType id="0x0101" />
                    <AllowedContentType id="0x0101008CF1CDE6AF3C644E93569474BF0D9DFD" />
                    <AllowedContentType id="0x0101009A5057FEE7BD674EB0C0B448F20F833E" />
                    <AllowedContentType id="0x0101009148F5A04DDD49CBA7127AADA5FB792B00AADE34325A8B49CDA8BB4DB53328F214" />
              </act:AllowedContentTypes>
           </XmlDocument>
           <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/sharedfields">
               <sf:SharedFields xmlns:sf="http://schemas.microsoft.com/office/documentsets/sharedfields" LastModified="05/12/2012 13:23:22">
                    <SharedField id="cbb92da4-fd46-4c7d-af6c-3128c2a5576e" />
                    <SharedField id="413b4744-f709-4952-8864-fd38e60bd754" />
                    <SharedField id="475c2610-c157-4b91-9e2d-6855031b3538" />
                    <SharedField id="7cf8011a-9d47-44f4-a49e-f72d6b038161" />
                    <SharedField id="2defb7a6-aca0-43bc-8d73-cbd07e5b7503" />
               </sf:SharedFields>
           </XmlDocument>
           <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/defaultdocuments">
               <dd:DefaultDocuments xmlns:dd="http://schemas.microsoft.com/office/documentsets/defaultdocuments" LastModified="05/12/2012 13:23:22" AddSetName="True">
                    <DefaultDocument name="Loan Application Form.docx" idContentType="0x0101008CF1CDE6AF3C644E93569474BF0D9DFD" />
               </dd:DefaultDocuments>
           </XmlDocument>
           <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/welcomepagefields">
               <wpf:WelcomePageFields xmlns:wpf="http://schemas.microsoft.com/office/documentsets/welcomepagefields" LastModified="05/12/2012 13:23:22">
                    <WelcomePageField id="475c2610-c157-4b91-9e2d-6855031b3538" />
                    <WelcomePageField id="2defb7a6-aca0-43bc-8d73-cbd07e5b7503" />
                    <WelcomePageField id="413b4744-f709-4952-8864-fd38e60bd754" />
                    <WelcomePageField id="7cf8011a-9d47-44f4-a49e-f72d6b038161" />
               </wpf:WelcomePageFields>
            </XmlDocument>
            <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
               <FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
                    <Display>ListForm</Display>
                    <Edit>ListForm</Edit>
                    <New>DocSetDisplayForm</New>
               </FormTemplates>
            </XmlDocument>
            <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
               <FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
                    <New>_layouts/NewDocSet.aspx</New>
               </FormUrls>
            </XmlDocument>
        </XmlDocuments>
    </ContentType>
</Elements>


16.   Create three features WebParts, Content Types and Site Columns. Associate the element manifests with them so that package designer looks like this.

Figure 13 : Package Designer

17.   Deploy the project to another site collection. Check whether Loan Proposal content type is created exactly like that of created through browser.
18.   This is how it looks when a Loan Proposal document set is created. 

Figure 14 : Loan Proposal Document Set in usage

That was easy! Please leave a comment in case of any queries. Source code can be downloaded here.

Tributes to all loving mothers on this Mother’s Day!

Saturday, 5 May 2012

Image Webpart Color Picker Tool Part


I remember my role model who taught and got me started in SharePoint, one day told me that if you want to learn SharePoint, then better learn it from SharePoint. You will find SharePoint doing same what it expects from developers to create any SharePoint artifacts...hence SharePointRoot/ 12 hive folder is the best place to find working examples

This post talks about reusing some of the SharePoint pickers. SharePoint does use some nice pickers in many of its OOB functionalities. Take example of CQWP webpart. It uses Site/ List picker, Text Editors in its tool part. Many people have thrown light on how to reuse these pickers.

Site/ List Picker:

Custom Property Builders/ Text Editor:

Asset Picker/ Asset Url Picker:

Folder Picker:

There are many more... like audience picker, Picker containers etc.

Well, in my case, one of my clients wanted to have a color picker exactly like that of Image WebPart.

Figure 1: Image Webpart Color Picker


Hence I started digging into Image webpart code and it didn't take much to create a toolpart with Image webpart's color picker.

Figure 2: Custom Color Picker Toolpart


Unfortunately, this color pallet is very basic one and it does not have wide range of colors to pick from. But still, if you like it and want to use it in your assignments, here is the Source Code… Download the Source Code

Note:
The tooltip text used in tool part is not localized.

Time for a walk… daily walk of 4-5 kms is helping me against Insomnia.


Tuesday, 1 May 2012

SharePoint 2010: Workflow Association element


In MOSS 2007, WSS 3.0, associating workflow with list / content type is done programmatically usually in feature receiver code. This still holds true in SharePoint 2010 as well. I am not going into details about it but you can explore SPWorkflowAssociation API if you are interested. Instead, I am going to talk about new WorkflowAssociation element added in SharePoint 2010 which also can be used to associate the workflows to the list/ list content types or site in declarative (xml, no code) manner.

To start, I googled WorkflowAssociation element and to my surprise I have not found any documentation anywhere neither at MSDN not at any blogs. So I thought to figure it out myself. I manually associated Disposition Approval workflow to one my custom list through browser. Then I saved the site as site template, downloaded the site template wsp into my physical local drive, created new Visual Studio project using Import SharePoint Solution Package project template and selected the saved site template wsp. Bingo! One of the imported items shown by Visual Studio project wizard was the workflow association element created by me.

Figure 1 : Select Workflow Association item to Import


I selected it and clicked Finish button for import. Here is the generated workflow Association element manifest xml.


<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<WorkflowAssociation Name="Disposition Approval Workflow" 
                     Description="Manages document expiration and retention by allowing participants to decide whether to retain or delete expired documents." 
                     Id="{6947caf3-7463-4065-b79c-e5e3555f2629}" 
                     BaseTemplateId="{dd19a800-37c1-43c0-816d-f8eb5f4a4145}" 
                     ParentContentType="List" 
                     ParentList="Lists/Demo List" 
                     ParentWeb="TRUE" 
                     TaskList="Lists/Tasks" 
                     TaskListTitle="Tasks" 
                     HistoryList="Lists/Workflow History" 
                     HistoryListTitle="Workflow History" 
                     StatusColumnShown="FALSE" 
                     InternalNameStatusField="Disposit" 
                     RequireManagePermission="ViewListItems, EditListItems, ManageLists" 
                     Configuration="69659" 
                     AssociationData="" 
                     ContentTypeName="" 
                     ContentTypeId="0x" 
                     xmlns="http://schemas.microsoft.com/sharepoint/" />
</Elements>



Well, almost all attributes of the WorkflowAssociation tag are self-explanatory but still listing the purposes of them below. For some, even I have doubts. Leave the comment to either clarify those or in case of any discrepancy in my findings.

  • Name
    This is the name of the workflow association.
  • Description
    This is the description of the workflow association.
  • ID
    New GUID of the workflow association.
  • BaseTemplateId
    GUID of the workflow template which needs to be associated.
  • ParentContentType
    This determines whether workflow is associated to list, list content type or site.

    Possible Values
    When
    ContentType
    workflow is supposed to be associated with list content type.
    Web
    workflow is supposed to be associated with site
    List
    workflow is supposed to be associated with list

  • ParentList
    Name of the list to which workflow needs to be associated. In case of site workflow, leave the value as empty.
  • ParentWeb
    Not clear. I noticed that the value is always set to “TRUE” for both Site workflow and list workflows.
  • TaskList
    This is the Workflow Task list url to be used in workflow.
  • TaskListTitle
    This is the Workflow Task list name to be used in workflow.
  • HistoryList
  • This is the Workflow History list url to be used in workflow.
  • HistoryListTitle
    This is the Workflow History list name to be used in workflow.
  • StatusColumnShown
    TRUE/FALSE
  • InternalNameStatusField
    This should contain the internal name of the workflow status column.
  • RequireManagePermission
    Permissions to control who can execute the workflow.

    Possible permission Values
    When
    ViewListItems, EditListItems

    This is the default value.
    ViewListItems, EditListItems
    ManageLists
    When only users having ManageLists permission can execute the workflow

  • Configuration
    This is an integer number which controls when workflow is to be triggered. I have elaborated my findings about it separately in this blog below.
  • AssociationData
    This is workflow association data in xml format.
  • ContentTypeName
    Name of the list content type to which workflow needs to be associated. Leave it empty for list/ site workflows
  • ContentTypeId
    GUID of the list content type to which workflow needs to be associated. Remove this attribute as mentioning ContentType name attribute is enough.
  • CategoryName
    Not clear
  • RootWebOnly
    TRUE/ FALSE
Initially, I got confused with the different values of Configuration attribute generated by importing the Site Template wsp. Hence I tried digging into SharePoint assemblies for SPWorkflowAssociation API using disassembler tool Telerik JustDecompile to see what role does this attribute play and what are its possible values. I found it as a flagged Enum.

Figure 2 : Configuration Enum


Hence I quickly wrote a code which can be used to determine the configuration attribute value…


using System;
using System.Diagnostics;
using System.IO;
using System.Net;
namespace SPConsoleProject
{
    class Program
    {
        [Flags]
        public enum Configuration
        {
            None = 0,
            AutoStartAdd = 1,
            AutoStartChange = 2,
            AutoStartColumnChange = 4,
            AllowManualStart = 8,
            HasStatusColumn = 16,
            LockItem = 32,
            Declarative = 64,
            NoNewWorkflows = 128,
            MarkedForDelete = 512,
            GloballyDisabled = 1024,
            CompressInstanceData = 4096,
            SiteOverQuota = 8192,
            SiteWriteLocked = 16384,
            AllowAsyncManualStart = 32768,
            SkipContentTypePushDown = 65536
        }

        static void Main(string[] args)
        {
            Configuration a =   Configuration.SkipContentTypePushDown | 
                                Configuration.CompressInstanceData |
                                Configuration.AutoStartAdd |
                                Configuration.AllowManualStart | 
                                Configuration.AutoStartChange |
                                Configuration.HasStatusColumn;
            Console.WriteLine((int)a);
            Console.ReadLine();
        }
        
    }
}


Here are some examples of Configuration values with their corresponding enum values for Disposition Approval workflow.

Behavior
Value
Configuration Enum value
AllowManuallyAndOnCreated
69657
Configuration.SkipContentTypePushDown | 
Configuration.CompressInstanceData |
Configuration.AutoStartAdd |
Configuration.AllowManualStart | 
Configuration.HasStatusColumn

AllowManuallyAndOnCreatedAndOnEdited
69659
Configuration.SkipContentTypePushDown | 
Configuration.CompressInstanceData |
Configuration.AutoStartAdd |
Configuration.AllowManualStart | 
Configuration.AutoStartChange |
Configuration.HasStatusColumn

AllowManuallyAndOnEdited
69658
Configuration.SkipContentTypePushDown | 
Configuration.CompressInstanceData |
Configuration.AllowManualStart | 
Configuration.AutoStartChange |
Configuration.HasStatusColumn

For Site Workflow
4184
Configuration.CompressInstanceData |
Configuration.AllowManualStart |
Configuration.Declarative |
Configuration.HasStatusColumn


Well, I have mentioned all these details about Configuration value just to have some insights on how it is calculated. In real world, I don’t think you will need to bother about it as you can always generate your WorkflowAssociation element manifest file using method described at the start of this post.

Few other important findings/ suggestions:
  1. Ensure that the workflow status field mentioned InternalNameStatusField attribute is present in the list/ content type to which you want to associate the workflow. Otherwise, you will get some correlation errors for which you will need to dig the ULS logs.
  2. Do not mention the content type ID in case workflow is associated with the site or list content type. Content type name is sufficient.
  3. I was not able to create site content type workflow association. It was not captured when I saved the site as site template. Looks like a bug in Save as site template feature of SharePoint. Hope someone from Microsoft is reading.
  4. Ensure History or Tasks list to be used in workflow association is already present. Otherwise the feature activation will give file not found error.
  5. I am yet to determine the workflow association configuration when it should run on publication of major version of the document. I will give a try in free time. In case you figure it out before me, please leave a comment.

Thinking to create a Visual Studio Item Template for Workflow Association for my MP.SPDevExt project. Not decided though.