Sunday, December 21, 2008

SharePoint Protocols Documentation

Microsoft has released their SharePoint Products and Technologies Protocol Documentation. In their introduction they state the reason why this is published is to:

  • Ensure open connections;
  • Promote data portability;
  • Enhance support for industry standards;
  • Foster more open engagement with customers and the industry, including open source communities.

After skimming the documentation, it is not for the faint of heart nor reading material to read at your own leisure J I suspect the BizTalk guys would be more interested in reading this…

Thursday, December 18, 2008

SQL Server SP3 and SSRS SharePoint Integrated Mode

Many already know but SQL Server 2005 has released Service Pack 3. Here is the SQL SP3 Bug List. How does this affect MOSS – there is a fix for performance with SQL Reporting Services that run in SharePoint Integrated Mode.

"FIX: Rendering a report is much longer in the SharePoint integrated mode than in the Native mode, especially when you put more than one report viewer Web part on a SharePoint Web page"

A colleague (Steve Mann) stated he did some quick testing that resulted in:

  • Direct Report Rendering (clicking on rdl from a reports library) - 70% improvement on initial load; 50%-90% improvement on report refresh
  • Report rendering on page with the Report Viewer webpart - only saw a 15% improvement

Do not hold me to these stats. As well reporting services will not always be the performance problem…

However this is a pretty good improvement and you should consider rolling this out.

Wednesday, December 17, 2008

WF Workflow Custom Class Error

Error

After deploying your WF workflow and associating it to a list, you may see an issue where your workflow is immediately completed. When digging into the SharePoint logs, you will find the following:

DehydrateInstance: System.Runtime.Serialization.SerializationException: End of Stream encountered before parsing was completed. at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run() at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream) at System.Workflow.ComponentModel.Activity.Load(Stream stream, Activity outerActivity, IFormatter formatter) at System.Workflow.ComponentModel.Activity.Load(Stream stream, Activity outerActivity) at System.Workflow.Runtime.Hosting.WorkflowPersistenceService.RestoreFromDefaultSerializedForm(Byte[] activityBytes, Activity outerActivity) at Microsoft.SharePoint.Workflow.SPWinOePersistenceService.LoadWorkflowInstanceState(Guid instanceId) at System.Workflow.Runtime.WorkflowRuntime.InitializeExecutor(Guid instanceId, CreationContext context, WorkflowExecutor executor, WorkflowInstance workflowInstance) at System.Workflow.Runtime.WorkflowRuntime.Load(Guid key, CreationContext context, WorkflowInstance workflowInstance) at System.Workflow.Runtime.WorkflowRuntime.GetWorkflow(Guid instanceId) at Microsoft.SharePoint.Workflow.SPWinOeHostServices.DehydrateInstance(SPWorkflowInstance workflow)

Cause

What is happening is that you have a custom class as a local attribute of your workflow and WF does not know how deserialize and serialize your object instance. This is not problem with types like int, string, datetime, etc. but will be for any custom class.

This error is described as a pitfall that developers run into by the Microsoft SharePoint Team.

  • Pitfall: Re-using non-serializable SharePoint objects after rehydration
    Many non-workflow specific SharePoint objects, like SPListItem, are not serializable, so when the workflow dehydrates, these items become invalid. So if you try to use them when the workflow wakes up, you will get an error. To avoid this, refetch items if you're using them after a workflow rehydrates.
  • Pitfall: Forgetting to make custom classes serializable
    When creating your own class in a workflow, don't forget to add the "Serializable" attribute to the class. If you don't do this and declare a variable of that class in the workflow's scope, the workflow will fail when it tries to go to sleep and serialize the class. If you notice that the workflow "completes" when it isn't supposed to, this may be the culprit.

Ranting

From what few posting on this, I found people trying to create local copies of the association and initiation data in a generated class by using the xsd.exe tool. Their solution was to make it [System.NonSerialized] so that it is ignored as part of the hydration process. This really makes no sense because if you make the attribute NonSerialized the value is not retained across events and what is the point of making an object instance at higher level of scope if it is only used locally in a method? As well, the association and initiation data is always available to you through the SPWorkflowActivationProperties.

You will not get this error if you have custom classes that are instantiated within the scope of an event or method. So if you have a variable that is class level scope of the workflow, you will get this error. Ok – I digress…

Resolution

The resolution is really simple, you just need to have your classes support ISerializable and you will be off and running. The specific business rule required was that everyone needed to approve before continuing. I wanted to create a simple structure that would track how many task assignees and approved before continuing to the next step in the workflow. I really did not feel like create an external DB with persistence was warranted. Instead I wanted to create some custom classes and just let them have a lifetime with the workflow instance.

Here is my solution was pretty simple. Here is my TaskAssignee class.

    [Serializable()]
public class TaskAssignee : ISerializable
{
#region Attributes

private string _accountName;
private string _displayName;
private string _email;
private ContractApprovalAction _action;

#endregion

#region Constructor

public TaskAssignee()
{
_action = ContractApprovalAction.NoAction;
}

#endregion

#region Properties

public string AccountName
{
get
{
return _accountName;
}
set
{
_accountName = value;
}
}

public string DisplayName
{
get
{
return _displayName;
}
set
{
_displayName = value;
}
}

public string Email
{
get
{
return _email;
}
set
{
_email = value;
}
}

public ContractApprovalAction Action
{
get
{
return _action;
}
set
{
_action = value;
}
}

#endregion

#region ISerializable
//http://www.codeproject.com/KB/cs/objserial.aspx

//Deserialization constructor.
public TaskAssignee(SerializationInfo info, StreamingContext ctxt)
{
_accountName = (String)info.GetValue("AccountName", typeof(string));
_displayName = (String)info.GetValue("DisplayName", typeof(string));
_email = (String)info.GetValue("Email", typeof(string));
_action = (ContractApprovalAction)info.GetValue("Action", typeof(int));
}

//Serialization function.
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
info.AddValue("AccountName", _accountName);
info.AddValue("DisplayName", _displayName);
info.AddValue("Email", _email);
info.AddValue("Action", Convert.ToInt32(_action));
}

#endregion

}

Here is a class that wraps a collection of TaskAssignees which is also Serialized.

    [Serializable()]
public class TaskAssignees : ISerializable
{
#region Attributes

private List<TaskAssignee> _assignees;

#endregion

#region Constructor

public TaskAssignees()
{
_assignees = new List<TaskAssignee>();
}

#endregion

#region Properties

public List<TaskAssignee> Assignees
{
get
{
return _assignees;
}
}

#endregion

#region Methods

/// <summary>
/// Reset the task assignee list.
/// </summary>
private void Reset() {
_assignees = new List<TaskAssignee>();
}

#endregion

#region ISerializable
//http://blog.paranoidferret.com/index.php/2007/04/27/csharp-tutorial-serialize-objects-to-a-file/

//Deserialization constructor.
public TaskAssignees(SerializationInfo info, StreamingContext ctxt)
{
_assignees = (List<TaskAssignee>)info.GetValue("Assignees", typeof(List<TaskAssignee>));
}

//Serialization function.
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
info.AddValue("Assignees", _assignees);
}

#endregion

}

Finally I just add the following to my state workflow and now I can populated this on a task create event and then use the same object instance in a task changed event.

private TaskAssignees _taskAssignees;

References

Sunday, December 14, 2008

InfoPath Form Cannot be Found

Error

After you have deployed your workflow you will want to associate it to list. In my case I had an association form and I was getting an error stating "The form has been closed".
Not that valuable. Once you dig around deeper in the SharePoint logs you will find the following.

Exception occurred during request processing. (User: SHAREPOINT\administrator, Form Name: , IP: , Request: http://mossserver:28921/_layouts/CstWrkflIP.aspx?List={8ADE4F1B-8A6A-4BA7-A3BF-B4278E949E4A}, Form ID: , Type: InfoPathLocalizedException, Exception Message: The specified form cannot be found.)

Cause

Upon reading the error message you can pretty much tell the error has to do with locating the association form. This error can actually occur for any of your forms for the workflow.

Resolution

Well I was pretty annoyed that I got this error because I have been so careful in the past to ensure that I have the feature.xml and workflow.xml files set up properly. Things you should look for:

  • Make sure in your featue.xml you have the forms correctly referenced in the ElementFile tag.
  • Make sure you have the URNs set up correctly in the workflow.xml file (Association_FormURN, Instantiation_FormURN, Task0_FormURN, etc).
  • Make sure you have the .xsn files in the feature that is deployed.

My error was I had messed up my URNs because when I published the InfoPath form to the local network I changed the name form. Changing the name of the form, changes the URN – go figure; that was annoying. To get the URN by opening the form in InfoPath Design mode, click File, then properties. In my case, since I changed the name of the form, I had to go to the local network published .xsn file and get the urn from there.

I cannot say for certain that you will get this error for the first and third things you should look into that I noted above, however, just check.

Friday, December 12, 2008

Wrox Professional K2 blackpearl to the Presses

Get ready it is almost here, we have been writing Professional K2 blackpearl for Wrox. You can pre-order now on Amazon. I was just notified that it is going to the presses December 22nd and it will be available to customers January 15th. If you are a K2 professional, please consider getting this. Yes we had editors review our grammer. Hopefully we will give you a viewpoint from professionals who are implementing this for customers. Yours truly wrote the SmartObjects, InfoPath and Deployment chapters.

Please buy it!!!! I am the second row; second guy from the left having a bad hair day!

Thursday, December 11, 2008

Deploy InfoPath as Feature with Managed Code

Well, I had to break a rule but I decided to go ahead and do some simple .NET managed code on a web enabled InfoPath form I need to build. I followed my instructions on How to Deploy an InfoPath Form as a Feature and I started to get errors when accessing my form.

Error Message

An exception occurred during loading of business logic. (User: SHAREPOINT\administrator, Form Name: ContractsApprovalRequestForm, IP: , Request: http://mossserver:28921/_layouts/FormServer.aspx?XsnLocation=http://mossserver:28921/FormServerTemplates/ContractsApprovalRequestForm.xsn&SaveLocation=http://mossserver:28921/contractsapproval/Contract+Approval&Source=http://mossserver:28921/contractsapproval/Contract%2520Approval/Forms/AllItems.aspx&DefaultItemOpen=1, Form ID: urn:schemas-microsoft-com:office:infopath:ContractsApprovalRequestForm:-myXSD-2008-12-12T02-14-05, Type: FileNotFoundException, Exception Message: Could not load file or assembly 'file:///C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\Template\Features\ContractsApproval.Form.Request\ContractsApproval.Form.Request.dll' or one of its dependencies. The system cannot find the file specified.)

Cause

Since the form was installed to SharePoint using a Feature the apparently the DLL that is part of the XSN is not extracted by Form Services when the form is installed as a Feature. For detailed information read KB 930894. What I found out is that this is a common issue for InfoPath association, initiation and task forms that are part of a workflow. I had not run into this because I have tried to not use .NET managed code when doing InfoPath development.

Resolution

The solution is to get the dll for the InfoPath form and place it with the XSN. In this case, it would be in the Feature folder for the InfoPath form in the 12 Hive. Installing it in the GAC of the machine does not resolve the issue…

To resolve this in my How to Deploy an InfoPath Form as a Feature blog, modify the WSP.ddf to grab the dll and package it up in the WSP.

When to Write Custom Code in K2

I had two K2 colleagues contact me at the same time roughly about the same thing. Neither are new to K2 however everyone at one time or another runs across this. At what point do I just put up a custom event and just write some .Net code?

Yes – the wizards of K2 are tremendously helpful however configuring activities and line rules can become annoying to a developer. Especially if you have complex logic and the diagram you would end up drawing is more trouble than what is worth. I really do not suggest trying to model every "if statement" in your process as a line rule. It really does work well. For instance you may have a point in your workflow where you need to send an email. You may have business logic that based on the state of the process instance you may have to insert different text into the body of the email. When using the email template, you will quickly find out that you cannot create a single email event. What you will have to do is create multiple email events with line rules that take you down to each one. The moment you have more than four or five email activities, with relatively the same content other than some small deviations, you will really start to hate K2. All I can say is forget using the email template and start writing some custom code. Dan (colleague at RDA) wrote this blog. Here he shows how to create a utility class, create a reference to the library and a start using. In K2.net 2003 we had code modules, but they are no more. So this is how to do it with K2 blackpearl.

When I have heavy InfoPath processes with lots InfoPath events, I really start hating life when configuring all of the activities. In those cases, I really cannot do the approach above. However, it is better than the alternative of custom coding it in WF. I rather be clicking for two days than building for two weeks.

Still somehow I never get away without writing custom code. Even when I work with SmartObjects, I prefer to work with the API directly. I really love K2 and WF because they allow me to model the major steps of the process visually and just click into the code where all the real work is. It gets the core business logic out of the User Interface Layer. I really start like workflow technologies when I have the ability to take snapshots of the workflow and send them to the business user so they can see how the core logic of the application is built.

Tuesday, December 9, 2008

Free Adobe 64 bit iFilter

Adobe has finally released their free 64-bit PDF iFilter for MOSS. If you do not know what this is for; installing this will allow PDFs uploaded onto SharePoint to be searchable. No need any more to buy FoxIt.

Information - http://blogs.adobe.com/acrobat/2008/12/adobe_pdf_ifilter_9_for_64bit.html

Download - http://www.adobe.com/support/downloads/detail.jsp?ftpID=4025