Wednesday, September 25, 2013

SharePoint Synchronous Event Receiver with message to UI and saving form data

The goal is to save data from the new/edit form to list item and in certain situations show a message to UI by using event receivers.

As we all know showing a message to UI is not going to work when using async event receivers. So we’re stuck to use sync event receivers. At first you will see examples that shows a message by using code like this:

try
{
    //some code
}
catch (Exception ex)
{
    properties.Status = SPEventReceiverStatus.CancelWithError;
    properties.ErrorMessage = ex.Message;
    properties.Cancel = true;
}

The options for properties.Status are:
Continue, there is no error.
CancelNoError, silently cancel the request as if the request has succeeded.
CancelWithError, cancel the request with the error message specified in ErrorMessage.
CancelWithRedirectUrl cancel the request and redirect to the URL specified in RedirectUrl.

The only usable is CancelWithRedirectUrl. However this will not save your form data to the listitem. So all changes are lost.
Where is the option to ‘ContinueWithRedirectUrl’? It doesn’t exist.
Luckily we have the SPUtility class with the Redirect method. When using the SPRedirectFlag.DoNotEndResponse flag it will not abort the event receiver thread (Flag Default will abort event receiver thread and therefor will not save the afterproperties on the list item.). In this case the event receiver thread will be completed and afterproperties are save to the list item. And the redirect will be processed. You could redirect a user to a custom page, provide some parameters and show a message as you like.

Code sample of the redirect:

SPUtility.Redirect("notificationpage.aspx?status=somestatusvalue", SPRedirectFlags.DoNotEndResponse, HttpContext.Current);

Wednesday, June 12, 2013

Copy SPField property values without changing Content Type

In a particular project for a client I needed to copy all field properties from document A to document B. However the content type should not be changed. For simplicity I ignore edge cases. For example in case the source and destination content type have different required SPFields.

At first I copied all available fields from source to destination:

foreach (SPField field in sourceItem.Fields)
{
    if (field.ReadOnlyField)
    {
        continue;
    }
    if (destinationListitem.Fields.ContainsField(field.InternalName))
    {
        destinationListitem[field.InternalName] = sourceItem[field.InternalName];
    }
}
destinationListitem.SystemUpdate()

The content type changed. No wonder as the SPField ‘ContentType’ was copied.
Changed code to to ignore SPField ‘ContentType’

IEnumerable<string> ignoreFields = new List<string>()
{
    "ContentType"
};
 
foreach (SPField field in sourceItem.Fields)
{
    if (field.ReadOnlyField)
    {
        continue;
    }
    if (ignoreFields.Contains(field.InternalName, StringComparer.InvariantCultureIgnoreCase))
    {
        continue;
    }
 
    if (destinationListitem.Fields.ContainsField(field.InternalName))
    {
        destinationListitem[field.InternalName] = sourceItem[field.InternalName];
    }
}
destinationListitem.SystemUpdate()

But still content type changed.
It turns out that you need to skip the SPField ‘ContentType’ and ‘MetaInfo’ to make sure the content type of the destination item is not changed.

Final code snippet:

IEnumerable<string> ignoreFields = new List<string>()
{
    "ContentType",
    "MetaInfo"
};
 
foreach (SPField field in sourceItem.Fields)
{
    if (field.ReadOnlyField)
    {
        continue;
    }
    if (ignoreFields.Contains(field.InternalName, StringComparer.InvariantCultureIgnoreCase))
    {
        continue;
    }
 
    if (destinationListitem.Fields.ContainsField(field.InternalName))
    {
        destinationListitem[field.InternalName] = sourceItem[field.InternalName];
    }
}
destinationListitem.SystemUpdate()