James Mann's Blog

The blog of James Mann

Generate my namespace based on InfoPath URN

There is a direct correlation between a form template URN and the my namespace uri that is required by infopath forms referencing the template.

This little method will give you the namespace required for a given template URN. This is nothing complex, but is handy nontheless.

public static string GetInfoPathNamespaceForUrn(string urn)
{
  string inputdate = new Regex(".*myXSD-(?<date>.*)").Match(urn).Groups["date"].Value;
  string outputdate = DateTime.ParseExact(inputdate, "yyyy-MM-ddTHH-mm-ss", CultureInfo.InvariantCulture).ToString("yyyy-MM-ddTHH:mm:ss");
  return string.Format(@"http://schemas.microsoft.com/office/infopath/2003/myXSD/{0}", outputdate);
}

 

Enumerate web applications on a sharepoint farm

To enumerate site collections on a farm:
SPWebService contentService = SPWebService.ContentService;

foreach(SPWebApplication app in contentService.WebApplications) 
{
  if (app.Sites.Count > 0) 
  {
    // pick the first site (root site)
    SPSite rootSite = app.Sites[0];
    
    Trace.WriteLine(site.Url);
  }
}

Get the item a SharePoint workflow task is associated with

This is handy. SharePoint helpfully populates the meta data with the GUID of the list and the ID of the item a WF instance is associated with. These are stored in "ows_WorkflowListId" and "ows_WorkflowItemId" fields on the task list item.

 An example of using this is from an InfoPath form in the form load code behind.

SPListItem currentListItem = SPContext.Current.ListItem;
if (currentListItem != null)
{
  object associatedWfListId = currentListItem["ows_WorkflowListId"];
  object associatedWfItemId = currentListItem["ows_WorkflowItemId"];
 
  if (associatedWfItemId != null && associatedWfListId != null)
  {
    SPListItem item = web.Lists.GetList(new Guid(associatedWfListId.ToString()), false).GetItemById(int.Parse(associatedWfItemId.ToString()));
    // THE ABOVE ITEM IS THE ASSOCIATED LIST ITEM
  }
}

Part 1/2: Integrating a SharePoint workflow with TFS / eScrum - Creating the product

As part of a project tracking workflow we have various points in the lifecycle of a project where we want to poke data into TFS, and have that data exposed through eScrum.

In particular, we need to:

  1. Create a new eScrum Product (at the inception of the project)
  2. Create eScrum Product Backlog items (on a weekly cycle)

After a bit of digging and dissassembling it turns out that it is not that hard to do. The first of the two above tasks implementation is as follows:(note that the eScrumRootAreaPathURI is the URI of the eScrum Team Project - you can find this URI in the team explorer in VS - it will look something like vstfs:///Classification/TeamProject/a1bac9db-c321-4fe6-8a12-fa19b4c2abab):

 

public static void CreateEScrumProduct(string name, string eScrumRootAreaPathURI, string tfsurl, string username, string password, string host)
{

    NetworkCredential account = new NetworkCredential(username, password, host);
    TeamFoundationServer server = new TeamFoundationServer(tfsurl, account);
    server.Authenticate();     /**
     * first the area path for the product must be created in the eScrum team project.
     */
    ICommonStructureService ics = (ICommonStructureService)server.GetService(typeof(ICommonStructureService));
    NodeInfo[] ni = ics.ListStructures(eScrumRootAreaPathURI);

    foreach (NodeInfo n in ni)
    {
        if (n.StructureType == "ProjectModelHierarchy")
        {
            ics.CreateNode(name, n.Uri);
            break;
        }
    }

    /**
     * now a new eScrum product details work item must be created referencing the above
     * area path. note that this can fail due to the tfs warehouse not refreshing
     * so it is brute forced until a better way is apparent.
     */
    Thread t = new Thread(delegate()
    {
        bool cont = true;
        while (cont)
        {
            try
            {
                WorkItemStore store = new WorkItemStore(server);
                WorkItem item = new WorkItem(store.Projects["Escrum"].WorkItemTypes["eScrum Product Details"]);
                item.Title = name;
                item.Fields["Area Path"].Value = @"Escrum\" + name;
                item.Fields["Assigned To"].Value = "Escrum Team";
                item.Save();

                cont = false;

            }
            catch (Exception ex)
            {
                Trace.WriteLine("failed saving... attempting again in 1000ms");
                Thread.Sleep(1000);
                cont = true;
            }
        }
    });
    t.Start();
} 
Technorati Tags: ,,,

Creating Multiple Messages in a BizTalk Server disassemble pipeline component

There is a interface IBaseMessageFactory that you can use to create instances of BizTalk messages and BizTalk message parts.

You can grab a handle to an instance of a subtype of this interface from within the Dissassemble method using the IPipelineContext.GetMessageFactory().

An important thing to note is that in order to preserve the message context you must grab this from the original message and poke it into the new instances created by the factory. The following code illustrates it.

public void Disassemble(Microsoft.BizTalk.Component.Interop.IPipelineContext pc, Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
{
IBaseMessageContext sourceContext = inmsg.Context;
IBaseMessagePart part = inmsg.BodyPart;

// queue four messages to be processed in the GetNext() method.
// _msgs is a simple Queue
_msgs.Enqueue(CreateMessage(pc, sourceContext,part));
_msgs.Enqueue(CreateMessage(pc, sourceContext,part));
_msgs.Enqueue(CreateMessage(pc, sourceContext,part));
_msgs.Enqueue(CreateMessage(pc, sourceContext,part));
}

private string messageType = @"http://www.myschema.com/schemas/myschemamessage/MySchema";
private string systemPropertiesNamespace = @"http://schemas.microsoft.com/BizTalk/2003/system-properties";

public IBaseMessage CreateMessage(IPipelineContext pc, IBaseMessageContext sourceContext, IBaseMessagePart part)
{
IBaseMessage msg = pc.GetMessageFactory().CreateMessage();
msg.AddPart("Body", part, true);


// comment out the following line to preserve the original message
msg.BodyPart.Data = new MemoryStream(ASCIIEncoding.ASCII.GetBytes("blah blah blah"));
msg.Context = sourceContext;
msg.Context.Promote("MessageType", systemPropertiesNamespace, messageType);
return msg;
}

Persisting state in Sharepoint timer job definitions

Using class member variables will not work if you want to persist state between invocations. This seems to be due to the way sharepoint manages timer jobs. A solution is to use the Properties *property* which is exposed to all SPJobDefinition subtypes. This is a hashtable which accepts a key value pair. Usage:

public class SampleTimerJob : SPJobDefinition
{
    public SampleTimerJob(string jobName, SPWebApplication webApplication, string url, string email)
        : base(jobName, webApplication, null, SPJobLockType.ContentDatabase)
    {
        ContainerSite = url;
        Email = email;

        this.Title = "sample timer";
    }

    public string ContainerSite
    {
        get { return Properties["site"].ToString(); }
        set { Properties["site"] = value; }
    }

    public string Email
    {
        get { return Properties["email"].ToString(); }
        set { Properties["email"] = value; }
    }

    public override void Execute(Guid targetInstanceId)
    {
        SPSite site = new SPSite(ContainerSite);

        using (SPWeb web = site.OpenWeb())
        {

            SPUtility.SendEmail(web, false, false, Email,
                                "title",
                                "content");
        }
    }
}

Sharepoint and TaskListContentTypeId with mixing Office workflow tasks with regular sharepoint workflow tasks. Part 2

The reason the Office Sharepoint Server Workflow Task is not visible is because it is assigned to a special group named "_Hidden" which precludes it from being displayed in the site collection content type view.

As it turns out content types are exposed and manipulable through the Sharepoint OM using the class SPContentType. You can get a list of content types from a SPWeb by invoking the getter on property ContentTypes:

SPSite site = new SPSite("http://splaptop");
using (SPWeb web = site.OpenWeb())
{
  foreach (SPContentType contentType in web.ContentTypes)
  {
    if (contentType.Name == "Office SharePoint Server Workflow Task")
    {
      /*
       * make a member of whatever group you like...
       * nb. if group is == "_Hidden" (default) then it will not show up
       * in the list of content types
       */
      contentType.Group = "MOSS";
      contentType.Update();
    }
  }
} 
The content type will now be visible when you try to assign it to a task list.

 

Sharepoint and TaskListContentTypeId with mixing Office workflow tasks with regular sharepoint workflow tasks.

We have a workflow that is a mix of simple tasks and MOSS infopath forms server workflow tasks.

To that end we remove the TaskListContentTypeId from workflow.xml and use the CreateTaskWithContentType shape to manually set the content type on appropriate tasks. This works fine until we come to deploy on a fresh sharepoint site.

We get some kind of error in the sharepoint logs:

10/15/2007 15:29:09.76  w3wp.exe (0x145C)                        0x144C Windows SharePoint Services    Workflow Infrastructure        72ew Medium   Object reference not set to an instance of an object.  
10/15/2007 15:29:09.78  w3wp.exe (0x145C)                        0x144C Windows SharePoint Services    Workflow Infrastructure        88xr Unexpected WinWF Internal Error, terminating workflow Id# c345e730-f40d-4cb9-8197-f472143c8410  
10/15/2007 15:29:09.78  w3wp.exe (0x145C)                        0x144C Windows SharePoint Services    Workflow Infrastructure        98d4 Unexpected System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object.     at Microsoft.SharePoint.Workflow.SPWinOETaskService.CreateTaskWithContentTypeInternal(Guid taskId, SPWorkflowTaskProperties properties, Boolean useDefaultContentType, SPContentTypeId ctid, HybridDictionary specialPermissions)     at Microsoft.SharePoint.Workflow.SPWinOETaskService.CreateTaskWithContentType(Guid taskId, SPWorkflowTaskProperties properties, String taskContentTypeId, HybridDictionary specialPermissions)     --- End of inner exception stack trace ---     at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAtt...  
10/15/2007 15:29:09.78* w3wp.exe (0x145C)                        0x144C Windows SharePoint Services    Workflow Infrastructure        98d4 Unexpected ...ributes, RuntimeTypeHandle typeOwner)     at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)     at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)     at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)     at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)     at System.Workflow.Activities.CallExternalMethodActivity.Execute(Acti...  
10/15/2007 15:29:09.78* w3wp.exe (0x145C)                        0x144C Windows SharePoint Services    Workflow Infrastructure        98d4 Unexpected ...vityExecutionContext executionContext)     at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext)     at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext)     at System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime)     at System.Workflow.Runtime.Scheduler.Run()

Which tells us a little, but not much.

So after a few :ahem: hours of digging around. It turns out that the problem manifests itself because when a tasklist for a wf instance is created by associating a workflow with a list - it sets up content types in the task list which sharepoint uses to marshal tasks. We weren't seeing the problem because we only started using CreateTaskWithContentType activities half way through the development cycle.

We can see this by looking at the task list, going to "Settings"..."Advanced Settings", and checking "Allow management of content types". OK that and scroll down the summary screen and there *should* be a content type named "Office SharePoint Server Workflow Task". If there is not you need to associate the content type with the task list and everything should be tickety boo. (you can do this quick and dirty by adding the content type id to the workflow.xml and recreating the task list when you assign the workflow).

If you click "Add from existing site content types" the type will not be visible. I will update when I find how you do this.