Category Archives: Sharepoint

Is Duet Enterprise Workflow the weakest link?

Duet Enterprise FP1, as great as it sounds, forms a glue between Microsoft and SAP. This blog post talks about how Duet Enterprise workflow may be the weakest link of features that comes with Duet Enterprise.

In a nutshell, workflow in Duet Enterprise is implemented as two-way communication between SharePoint and SAP Netweaver Gateway. End users in SharePoint see workflow tasks with action buttons, nothing different from the way they would approve or reject a sharepoint workflow task, because it is a sharepoint workflow task. All great and good, but under the covers, implementation is not as elegant as it look.

Workflows in Duet Enterprise are exposed per user-decision interaction step, more simply, this is the diamond shapes in a workflow sketch. At its plain configuration, each one of this interaction step is mapped as a subsite which hosts workflow task for this ‘type’ in SharePoint side. Yes, one step – one subsite.

Workflow mapping

Source: Plan for SAP workflow tasks for Duet Enterprise

This would mean that administrators and developers would need to maintain a list of user decision points which has been exposed. For SAP, it means at any time when any of these decision points change, they may need to reconfigure workflow pattern customizations and/or filters. Then all changes would need to be captured and documented clearly and handed over to SharePoint administrators and/or developers.

On the SharePoint side, Microsoft seems to be missing a few core operations that make this maintenance even harder. A workflow task type is configured in SharePoint by specifying task type and outcomes as they appear in SAP. For example, task of type CampaignApproval has been configured with outcomes Approve=001 and Reject=002.

When the scenario where additional outcome has been configured on SAP side (eg. Revision Required=003), this CampaignApproval subsite has to be reconfigured in SharePoint, which is fair. Question is, how?

1. Deleting the workflow subsite will not work, because then the workflow parent site still thinks that the workflow type is still configured and render the task not usable, and we won’t be able to create a task with the same name either.

2. Changing the outcomes in sharepoint designer and republish workflow might work, but really?

There are few options provided in the site actions menu, but it looks like when one is configured, there is no way of reversing it, modifying it and clean it up properly without destroying the whole site altogether.

Workflow config options

I am puzzled indeed. May look into doing it programmatically next, but certainly Microsoft, I wonder if this has been thought through?

Must have tools for BCS development

Creating a BCS model and external list with SharePoint Designer 2010 makes things a breeze: just few clicks and we are done, but this typically does not work in development environment. Why? Simply because we need to package this up, and to package this up, we need to be able to extract the BCS artifacts into a Visual Studio solution.

Well, lucky that there are tools for that, check out this blog post by Russell Palmer. These tools are:

  1. BCS Artifact generator
  2. BCS Solution Packaging tool

Writing to event log and ULS in Sharepoint 2010

No logging means no visibility

Logging is a prime and most basic feature that any software need to have, and there are many ways to implement logging. In this post, I will illustrate a way to implement logging to windows application event log and/or the ULS when developing custom sharepoint 2010 solutions.

The assumption here is that the solution will be deployed as a farm solution.

A log entry can have various attributes or properties, but most likely we would want to use the following attributes:

  1. Source
  2. Severity
  3. Correlation ID
  4. Message

When writing to the application event log, it is advisable to create the event sources beforehand. Identify your event sources that you want to use and create these sources, one way is to use the script in this post.

Typically I would advise against logging to the application event log on production systems, for sharepoint solutions, use the ULS, but alas, reading ULS every time things goes wrong can be time consuming, yeah we have great tools like the ULS viewer, but it is still not as easy to read as the event viewer. Therefore, I typically write my logs to the application event log on my DEV machine.

For this, I need a switch that enables me to direct all my log entries to the application event log, the ULS, or both (or other sources). Astute .NET developers would probably think of Enterprise Library when it comes to this scenario. Yes EntLib is collection of great components, but if all we want is a simple logger that writes to two sources, we might as well write common logic and be done in 30 minutes or less rather than trying to bake in and configure EntLib, think about the web.config changes you have to do with EntLib.

For me, my switch is simply a couple of constants in a static class:

namespace Department.Solution.Common
{
    public static class Constants
    {
        public static class AppSettings
        {
            public const bool LOG_TRACE = true;
            public const bool LOG_EVENTLOG = true;
        }
    }
}

We should also ideally put our event sources in constants:

namespace Department.Solution.Common
{
    public static class Constants
    {
        public static class LogSource
        {
            public const string LOGSRC_COMMON = "ProductXYZ Common";
            public const string LOGSRC_WEBPART = "ProductXYZ WebParts";
        }
    }
}

Then I have these methods in my common logger class to write to event log and/or the ULS depending on those flags:

private static void WriteToTrace(Guid correlationGuid, string source, string message, EventLogEntryType entry)
{
    if (!Constants.AppSettings.LOG_TRACE) return;
    var diagSvc = SPDiagnosticsService.Local;
    var traceSeverity = TraceSeverity.Unexpected;
    var evtSeverity = EventSeverity.Error;

    switch (entry)
    {
        case EventLogEntryType.Information:
        case EventLogEntryType.FailureAudit:
        case EventLogEntryType.SuccessAudit:
            traceSeverity = TraceSeverity.Verbose;
            evtSeverity = EventSeverity.Information;
            break;
        case EventLogEntryType.Warning:
            traceSeverity = TraceSeverity.Medium;
            evtSeverity = EventSeverity.Warning;
            break;
        default:
            traceSeverity = TraceSeverity.Unexpected;
            evtSeverity = EventSeverity.Error;
            break;
    }
    SPSecurity.RunWithElevatedPrivileges(() =>
        diagSvc.WriteTrace(0,
        new SPDiagnosticsCategory(source, traceSeverity, evtSeverity), traceSeverity,
        "Correlation Guid {0}: {1}", new object[] { correlationGuid.ToString("B"), message })
    );
}

private static void WriteEventLogEntry(Guid correlationGuid, string source, string message, EventLogEntryType entry)
{
    if (!Constants.AppSettings.LOG_EVENTLOG) return;
    SPSecurity.RunWithElevatedPrivileges(() =>
            {
                var appLog = new EventLog { Source = source };
                appLog.WriteEntry(string.Format("Correlation Guid {0}{1}{2}", correlationGuid.ToString("B"), System.Environment.NewLine, message), entry);
            });
}

Simply put wrappers and overrides around these two methods, for example:

public static string LogError(string source, string format, params object[] args)
{
    return LogError(source, string.Format(format, args));
}
public static string LogError(string source, string message)
{
    Guid correlationGuid = Guid.NewGuid();
    WriteEventLogEntry(correlationGuid, source, message, EventLogEntryType.Error);
    WriteToTrace(correlationGuid, source, message, EventLogEntryType.Error);
    return correlationGuid.ToString();
}

Then whenever you want to log anything, simply call the public method and pass in your source string in one line (for example, if the logger is implemented within your custom LoggerInstance class):

LoggerInstance.LogError(Constants.LogSource.LOGSRC_WEBPART, "Woops, web part {0} errored out!", webPart.Title);

Logging would be a breeze if you have this helper class, because it simply provide a layer of abstraction.

Viewing XML data on Content by Query Web Part (CQWP)

How many times have you been tasked to customize a Content by Query Web Part in SharePoint? In my case, this task often involves customizing the XSL used by the web part. The pain is, without knowing the data we are working with, we are basically trying to aim our arrows in darkness.

If we can see the data (ie, XML) that we are dealing with, then it would become much easier to write an XSL that achieves what we want. This post aims to give you a way to dump out the XML content of the content query web part to allow you to visualize what you are working with.

To do this, first step is to download a copy of the default xsl that all CQWP uses, this usually exist in Style Library/XSL Style Sheets subfolder and is called ContentQueryMain.xsl
Download this to your local folder now, and rename it as something else, eg. ContentQueryDump.xsl

Now open the ContentQueryDump.xsl file in your favourite XSL editor or text editor, and locate the template root node

Change the inner XML of this node to dump out the xml content by surrounding it in TEXTAREA tag

<xsl:template match="/">
    <TEXTAREA style="width:200px;" rows="10">
        <xsl:copy-of select="*"/>
    </TEXTAREA>
</xsl:template>

Save this file, and upload it to the Style Library within the XSL Style Sheets folder.

Now picture this: I have a custom list in my site that contains 2 custom columns, and I want a Content by Query Web Part to display data on this list according to the XSL that I specify.

I set up a content by query web part on my home page, set the query scope to my custom list, and this is what I get:

Let us take this web part and dump its XML data on screen. Export the web part from your page, and save it locally:

Crack it open in your favourite XML Editor or text editor, then locate the property node with name attribute of MainXslLink. Put the reference to the xsl file you uploaded earlier, as follows:

<property name="MainXslLink" type="string">
    /Style Library/XSL Style Sheets/ContentQueryDump.xsl
</property>

Save this .webpart file, then import and add this web part to the page

The result is as follows:

And voila! You have your content result XML, this is what your XSL will be looking at. You can see all the attributes, which makes things much easier from here onwards.

<dsQueryResponse>
  <Rows>
    <Row Title="Item 3" FileRef="/Lists/MyList/3_.000" Author="George Lucas" Editor="George Lucas" FSObjType="0" PermMask="0x400000300c231061" ProgId="" File_x005F_x0020_Type="" File_x005F_x0020_Size="" PublishingRollupImage="" Comments="" ID="3" Modified="2011-11-02 11:33:02" Created="2011-11-02 11:33:02" _Level="1" WebId="bf499d62-67d5-46af-9746-acd74885fa45" ListId="19877173-9e7a-4543-962b-c4dc4261aceb" _x007B_94f89715_x002D_e097_x002D_4e8b_x002D_ba79_x002D_ea02aa8b7adb_x007D_="Lists/MyList/3_.000" PubDate="Wed, 02 Nov 2011 00:33:02 GMT" FileExtension="" FileSize="" DocumentIconImageUrl="/_layouts/IMAGES/icgen.gif" LinkUrl="http://localhost/Lists/MyList/3_.000" Style="Default" GroupStyle="DefaultHeader" __begincolumn="True" __begingroup="False"></Row>
    <Row Title="Item 2" FileRef="/Lists/MyList/2_.000" Author="George Lucas" Editor="George Lucas" FSObjType="0" PermMask="0x400000300c231061" ProgId="" File_x005F_x0020_Type="" File_x005F_x0020_Size="" PublishingRollupImage="" Comments="" ID="2" Modified="2011-11-02 11:32:47" Created="2011-11-02 11:32:47" _Level="1" WebId="bf499d62-67d5-46af-9746-acd74885fa45" ListId="19877173-9e7a-4543-962b-c4dc4261aceb" _x007B_94f89715_x002D_e097_x002D_4e8b_x002D_ba79_x002D_ea02aa8b7adb_x007D_="Lists/MyList/2_.000" PubDate="Wed, 02 Nov 2011 00:32:47 GMT" FileExtension="" FileSize="" DocumentIconImageUrl="/_layouts/IMAGES/icgen.gif" LinkUrl="http://localhost/Lists/MyList/2_.000" Style="Default" GroupStyle="DefaultHeader" __begincolumn="False" __begingroup="False"></Row>
    <Row Title="Item 1" FileRef="/Lists/MyList/1_.000" Author="George Lucas" Editor="George Lucas" FSObjType="0" PermMask="0x400000300c231061" ProgId="" File_x005F_x0020_Type="" File_x005F_x0020_Size="" PublishingRollupImage="" Comments="" ID="1" Modified="2011-11-02 11:32:30" Created="2011-11-02 11:32:30" _Level="1" WebId="bf499d62-67d5-46af-9746-acd74885fa45" ListId="19877173-9e7a-4543-962b-c4dc4261aceb" _x007B_94f89715_x002D_e097_x002D_4e8b_x002D_ba79_x002D_ea02aa8b7adb_x007D_="Lists/MyList/1_.000" PubDate="Wed, 02 Nov 2011 00:32:30 GMT" FileExtension="" FileSize="" DocumentIconImageUrl="/_layouts/IMAGES/icgen.gif" LinkUrl="http://localhost/Lists/MyList/1_.000" Style="Default" GroupStyle="DefaultHeader" __begincolumn="False" __begingroup="False"></Row>
  </Rows>
</dsQueryResponse>

Using Reflection to set web part properties

Think about this scenario: you are tasked to do develop a custom SharePoint web part with a few custom properties. This custom web part will be removed and added back to a page during feature activation during a typical solution patch deployment process.
The requirement dictates that web part properties are preserved instead of being reset to their default value after this patch is deployed.

I tried to make a general solution to this by not worrying about the Type of the web part of which the properties we want to preserve. This makes use of Reflection in order to retrieve and set the web part properties. When I implement my web parts, I typically group custom properties in category groups by decorating the property with System.ComponentModel.CategoryAttribute attribute.

For example:

[System.Web.UI.WebControls.WebParts.WebBrowsable(true),
System.ComponentModel.Category("Parameters"),
Microsoft.SharePoint.WebPartPages.FriendlyName("Header Text"),
System.Web.UI.WebControls.WebParts.Personalizable(System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
System.Web.UI.WebControls.WebParts.WebDisplayName("Header Text"),
System.Web.UI.WebControls.WebParts.WebDescription("Header text for the grid")]
public string HeaderText { get; set; }

Right before you remove the old web part, use the following method to collect properties using reflection:

private Dictionary<string, object> CollectExistingProperties(System.Web.UI.WebControls.WebParts.WebPart existingWp)
{
    Dictionary<string, object> existingProperties = new Dictionary<string, object>();
    PropertyInfo[] properties = existingWp.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    if (properties == null) return existingProperties;
    foreach (PropertyInfo prop in properties)
    {
        object[] myAttributes = prop.GetCustomAttributes(typeof(System.ComponentModel.CategoryAttribute), true);
        if (myAttributes != null && myAttributes.Length > 0)
        {
            for (int j = 0; j < myAttributes.Length; j++)
            {
                if (string.Compare(((System.ComponentModel.CategoryAttribute)myAttributes[j]).Category, "Parameters", true) == 0)
                {
                    existingProperties.Add(prop.Name, prop.GetValue(existingWp, null));
                }
            }
        }
    }
    return existingProperties;
}

Then, for the new web part, use the following method to set custom properties which have been ‘preserved’ earlier:

private void SetPropertyCollection(ref System.Web.UI.WebControls.WebParts.WebPart webPart, Dictionary<string, object> preserved)
{
    PropertyInfo[] runtimeProperties = webPart.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    if (preserved != null && preserved.Count > 0 && runtimeProperties == null)
    {
        //Handle error
    }

    foreach (PropertyInfo prop in runtimeProperties)
    {
        object[] myAttributes = prop.GetCustomAttributes(typeof(System.ComponentModel.CategoryAttribute), true);
        if (myAttributes != null && myAttributes.Length > 0)
        {
            for (int j = 0; j < myAttributes.Length; j++)
            {
                if (string.Compare(((System.ComponentModel.CategoryAttribute)myAttributes[j]).Category, "Parameters", true) == 0)
                {
                    if (preserved.ContainsKey(prop.Name))
                    {
                        prop.SetValue(webPart, preserved[prop.Name], null);
                    }
                }
            }
        }
    }
}

When SPSecurityTrimmedControl AuthenticationRestrictions FAILs, simple workaround

SPSecurityTrimmedControl is a nice offering by Microsoft when it comes showing and hiding parts of your page/web part/control depending upon user permissions.

In my case, I have the requirement to show some parts of my page only to anonymous users, and some only to logged in users, hence giving the illusion of different context for logged on and logged out users while staying on the same page. I was ready to take advantage of the AuthenticationRestrictions property until I found out that it doesnt work when I give it the value of AnonymousUsersOnly

This blog entry by Waldek Mastykarz mentioned this same problem. I figured that the SPSecurityTrimmedControl class is not sealed, yeeha… that means I can extend it to fix this broken feature. I work around this by adding my own property to the extension of SPSecurityTrimmedControl class, and overriding the Render method of this class.

Simply add this class to your project:

[ParseChildren(false), Designer(typeof(SPControlDesigner)), SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true), AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal), SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
public class SecurityTrimmer : SPSecurityTrimmedControl
{
    private bool _AnonymousOnly = false;

    public bool AnonymousOnly
    {
        get { return _AnonymousOnly; }
        set { _AnonymousOnly = value; }
    }

    [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
    protected override void Render(HtmlTextWriter output)
    {
        if ((!AnonymousOnly && HttpContext.Current.Request.IsAuthenticated) || (AnonymousOnly && !HttpContext.Current.Request.IsAuthenticated))
        {
            base.Render(output);
        }
    }
}

Then I use this within my page by replacing the SPSecurityTrimmedControl with my own control:

<%@ Register TagPrefix="myuc" Namespace="[[Your Namespace here]]" Assembly="[[Your Assembly Name here]]"%>

<!-- your other markup -->

<myuc:SecurityTrimmer id="SecurityTrimmedControl1" runat="server" AnonymousOnly="True">
   Text visible only to anonymous users
</myuc:SecurityTrimmer>

And Voila, it works!

Cannot create SharePoint document workspace from Word

When we create a document and go “Create Document workspace”, sometimes we are told that we are Offline. This can usually happen if we have SharePoint and Office installed within the same box, typically I hit this in development VM.

What to do? Turn out that this is a known issue with SharePoint. To get around this:

  1. Go to Administrative Tools > Services
  2. Stop the System Event Notification service

And voila! Now we’re online!

%d bloggers like this: