Prexens team dev' corner

How-to: Develop your own Charting for SharePoint WebService

Overview
Web services offers you the best level of integration between Charting Web Parts and your databases or back-end systems. A powerful Web service mapping editor allows you to integrate any service that exposes its WSDL schema as a data source for a Chart Web Part. Contextual information extracted from SharePoint can be passed as input parameters to the Web service. Data are then retrieved asynchronously to maximize user experience, consumed by Chart Web Parts and exposed through visually-exciting charts.

Implementing a sample Web service

The simple chart below is generated from an external database, if you want to customize your charts, you can develop your own Web Service as explained in this post.
Charting

Chart Web Parts data series are based on the XML schema below. Compliant Web services have to output their results according to the following specifications.
 

<?xml version="1.0" encoding="utf-8"?>
<
xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<
xs:element name="Segments">
  <
xs:complexType>
    <
xs:sequence>
      <
xs:element maxOccurs="unbounded" name="Segment">
        <
xs:complexType>
          <
xs:attribute name="Title" type="xs:string" use="required" />
          <
xs:attribute name="Value" type="xs:unsignedByte" use="required" />
        </
xs:complexType>
      </
xs:element>
    </
xs:sequence>
  </
xs:complexType>
</
xs:element>
</
xs:schema>

The XML content below exposes a data series containing three segments A, B and C with respective values of 10, 15 and 25.

<?xml version="1.0" encoding="utf-8"?>
<
Segments>
   <
Segment Title="A" Value="10" />
   <
Segment Title="B" Value="15" />
   <
Segment Title="C" Value="25" />
</
Segments>

The following source code is a C# class that returns our sample data series and exposes it through an ASP.net Web service

File SampleSeriesService.cs
using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Serialization;
using System.Web.Services;

namespace ChartingForSharePoint
{
   public struct Segment
   {

     [XmlAttribute]
     public string Title;

     [XmlAttribute]
     public int Value;

     public Segment(string title, int value)
     {
       Title = title;
       Value = value;
     }
   }

   [WebService(Namespace = http://api.acme.com/services)]
   [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   public class SampleSeriesService : WebService
   {

     [WebMethod]
     [return: XmlRoot("Segments")]
     public List<Segment> GetSampleSeries(string inParam)
     {
        List<Segment> segments = new List<Segment>();
        segments.Add(new Segment("A", 10));
        segments.Add(new Segment("B", 15));
        segments.Add(new Segment("C", 25));
        return segments;
     }
   }
}

File SampleSeriesService.asmx

<%@ WebService Language="C#" Class="ChartingForSharePoint.SampleSeriesService,ChartingForSharePoint, Version=1.0.0.0, Culture=neutral,PublicKeyToken=f71dfbc876472393" %>


Registering the service

The C# class has to be compiled into a signed assembly. This assembly has to be registered into the GAC of your server, or copied into the bin directory of your Web application (in this case, make sure CAS policies are correctly configured for your assembly).

The ASMX file has to be copied at root level or into a subdirectory of folder C:\Program Files\Common file\Microsoft Shared\web server extensions\12\ISAPI on your Web front-end server.

The Web service is ready to run, but you must register it via an STSADM command in order to make SharePoint exposing its WSDL schema correctly. If you omit this step, no WSDL schema will be exposed when trying to access the service description via the special WSDL query string token.

Use the registerwebservice STSADM operation with syntax below to register the Web service to SharePoint. This custom STSADM command is automatically deployed when setting-up Charting for SharePoint on your system.

You can use the syntax below from a command prompt to obtain the list of parameters supported by the operation

C:\...\12\BIN>stsadm.exe -help registerwebservice

The table below gives details about each parameter

Parameter
Details Comments
url
Absolute URL of the Web service
If the ASMX file is stored in folder 12\ISAPI, the service URL will be http://<host>/<sitecollection-url>/_vti_bin/<service-file>.asmx
Note that you only need to register the service for one site collection on one Web application to make it globally available to Charting Web Parts
username
Login of the account used to access the service
This parameter is optional. You only need to specify credentials if the Web application uses basic authentication. Login can be composed of a username only, or it can contain a domain and a username separated by a backslash character (e.g. DOMAIN1\userA)
password

Password of the account used to access the service

Warning: the password will be sent in clear when calling the service

Below is a screenshot of how to register our sample service on the localhost Web application. If you receive the message Operation completed successfully, the Web service definition file is available to SharePoint.

Registering Web Service

You can ensure that the WSDL definition file is available by accessing the service URL from a Web browser and then click the Service description link, or by appending ?WSDL at the end of the service URL (see sample below)

http://localhost/_vti_bin/SampleSeriesService.asmx?WSDL

Authentication methods

Charting Web Parts are relying on Web browser credentials cache to access Web services. Thus, we advise you to connect to Web services located on the same Web application as Web Part pages. This way, Charting Web Parts are compliant with native SharePoint authentication methods:

  • Basic authentication
  • Windows integrated authentication
  • Forms authentication
  • Anonymous authentication

Using a Web service as a data source

1. From any Web Part page on your site collection, add a new Chart Web Part.

Using Web PArt

Note: make sure Charting for SharePoint is correctly deployed on your site collection. Refers to § Deploy features on a site collection for more details about setup procedure.

2. From the new Web Part, open the tool pane by clicking on the message displayed into the Web Part.

Open Mapping

3. From the Data mapping section of the tool pane, click on Edit mappings.

Edit Mapping

4. Select Web service as a data service type from the header of the popup window

Edit Web Service Mapping
5. Enter the absolute or relative service description URL into Address of the service description file. Then press Enter or click on the magnifier button

WebService URL
6. Choose a prefix and specify the service relative URL if you want to use relative path, or leave the default URL if you prefer an absolute URL.

Note: We advise you to work with relatives URLs for both service description and service address. You can then streamline support of extended Web applications.

7. Select a Web method from available operations. When a new operation is selected from the list, the corresponding input parameters are populated in the Input parameters mapping grid. The first parameter is ready for binding.

Web Method

8. For each input parameter, click on parameter name, map it to its contextual value by selecting a macro from the list of predefined values, and then click on the Save link. Repeat this operation for all input parameters.

Parameters

9. If no macro is convenient for your input parameter, you can hard code its value or use an expression builder registered for the Web application.

Note: if you want to improve the list of available macros with custom macros, you can register some ExpressionBuilder expressions, and add your custom macros into the following file

C:\...\12\TEMPLATE\XML\Prexens\SharePoint\ApplicationPages\Services\Mapping\ParameterMappingMacros.xml

10. When all settings are OK, click on the Save button. Settings are saved and the corresponding chart is displayed.

Charting

11. You can adjust the look & feel of the chart by editing chart’s settings from the tool pane. Different categories of settings are available:

  • Chart settings section give access to settings available for all chart types
  • Sections below the Chart settings section are more dedicated to one or more particular chart types.


Possible errors and resolutions

The table below is a non-exhaustive list of possible errors that can occurs when using mapping editor.

Error message
Resolution
Unable to cast object of type 'System.Web.Services.Description.HttpAddressBinding' to type 'System.Web.Services.Description.SoapAddressBinding'
 Try to register the Web service on the same Web application as the Web Part page. Refers to § “Registering the service” for more details about how to register a Web service.

There is an error in XML document (3, 2)

 WSDL description address is invalid. Make sure that you’ve appended the WSDL query string token to the URL of service description file. If yes, try to access the URL you’ve specified from a Web browser to ensure that it returns the WSDL schema of the service.
Exception: Protocol error
URL of the service description file is not well formed. Open a Web browser and make sure you can access the service with URL you’ve specified.
404 not found
The URL you’ve specified as the service description file address does not exist. Open a Web browser and make sure you can access the service with URL you’ve specified.

 


How-to: Replace ‘Submit for approval’ custom quick access button

Goal of this post

The goal of this post is to guide you through the different steps for replacing the ‘Submit for Approval’ quick access button in a WCM environment.


Problematic overview

‘Submit for Approval’ is a native button which aims at firing a ‘Parallel Approval’ workflow when Publishing features are activated  in a WCM web site:

 

Native Approval button

 

When deploying a custom approval workflow, you’ll probably need to overwrite the logic of this button to start your custom workflow correctly.
You would also be able to customize the icon and the text of your custom button to achieve something like this:

 

Custom approval button

 

Write a custom class CustomSubmitForApproval

 

1. Set pre-requisites:

 

  • First, add those tworeferences : Microsoft.SharePoint, Microsoft.SharePoint.Publishing
  • Create a public sealed class that inherits from:
    Microsoft.SharePoint.Publishing.WebControls.EditingMenuActions.ConsoleAction
  • Add a constant used as argument of post back events:
private const string PostBackEventArg = "submitForApproval";

 

2. Add a constructor and customize your custom quick access button:

public CustomSubmitForApprovalButton()
            : base()
{
      this.DisplayText = "Custom Submit for Approval";
      this.ImageUrl = "/_layouts/images/workflows.gif";
}

 

3. Override NavigateUrl property and fire post back events:

public override string NavigateUrl
{
     get
     {
          return String.Format("javascript:{0}", Page.ClientScript.GetPostBackEventReference(
this, CustomSubmitForApprovalButton.PostBackEventArg));
     }
}

 

4. Override RaisePostBackEvent method, and implement your redirection logic

 

In our case, we force the page check in and we redirect toward check-in page:

public override void RaisePostBackEvent(string eventArgument)
{
    try
    {
        if (eventArgument == CustomSubmitForApprovalButton.PostBackEventArg)
        {

            SPFile myFile = SPContext.Current.File;

            // Auto checkin page before redirecting to 'Publish Major Version'
            if (myFile.CheckOutStatus != SPFile.SPCheckOutStatus.None)
            {
                myFile.CheckIn("Default comment", SPCheckinType.MinorCheckIn);
            }

            string redirectUrl = String.Format(
                    "{0}_layouts/Checkin.aspx?FileName={1}&Publish=true&List={2}&Source={3}",
                    SPContext.Current.Web.ServerRelativeUrl,
                    SPContext.Current.ListItemServerRelativeUrl,
                    Convert.ToString(SPContext.Current.List.ID),
                    Page.Request.Url.OriginalString);

            SPUtility.Redirect(redirectUrl, SPRedirectFlags.Default, HttpContext.Current);
        }

    }
    catch (Exception ex)
    {
        ShowError(ex, new ConsoleError(ex.Message));
    }
}

 

You can override some properties to control what is the minimum level of permission required to access the feature, and what are the states causing your button to appear in the page editing toolbar. In samples below, users have to be granted the permission of adding items and page has to be a publishing page waiting for approval.

 

 

5. Override SPBasePermissions UserRights property

public override SPBasePermissions UserRights
{
    get { return SPBasePermissions.AddListItems; }
}
6. Override AuthoringStates RequiredStates property
public override AuthoringStates RequiredStates
{
    get
    {
        return AuthoringStates.IsPublishingPageTrue |
            AuthoringStates.SaveConflictExistsFalse |
            AuthoringStates.IsMajorVersionFalse |
            AuthoringStates.IsApprovalWorkflowConfiguredTrue |
            AuthoringStates.InEditModeFalse |
            AuthoringStates.IsApprovalWorkflowRunningFalse;
    }
}


 

Overwrite CustomQuickAccess.xml file in master pages gallery


When your custom class is compiled and deployed on your SharePoint server, you will have to overwrite CustomQuickAccess.xml located into MOSS master pages gallery.


In a live scenario, we recommend to deploy this file through activation event of a custom feature. Simply make sure not to override an already existing file.
In our sample scenario, we are going to edit this file directly from SharePoint Designer :


1. Open CustomQuickAccess.xml from /_catalogs/masterpage/Editing Menu


 

2. Add a reference to our custom class (mind to adjust assembly reference with your own assembly qualified name):

<references>
 <reference TagPrefix="Prexens"
      assembly="Prexens.SharePoint.CustomQuickAccessButton, 
Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5"
namespace="Prexens.SharePoint.CustomQuickAccessButton" /> </references>

3. Add a ConsoleNode that override native qaPublish Node ID:


<structure>
 <ConsoleNode 
Sequence="65"
  ConfigMenu="Replace"
  Action="Prexens:CustomSubmitForApprovalButton"
  ID="CustomQuickAccessButton_SubmitForApproval"
  ChangedNodeID="qaPublish" />
</structure>

 

4. Add two ConsoleNode that remove native Approve and Decline buttons (those two feature should probably be inconsistent with your custom publishing workflow):

<ConsoleNode ConfigMenu="Delete" ChangedNodeID="qaWorkflowDecline" />
<ConsoleNode ConfigMenu="Delete" ChangedNodeID="qaWorkflowApprove" />


5. Save, publish, and approve this file in master pages gallery


Retrieve profile information from members of an audience field

Goal of this post
 
By enabling audience targeting option on a list or library, you can display content only to people who are members of a particular group or audience.
 
Enable audience targeting
You can then specify audience members through Audience Targeting field.
Audience targeting field

But how to retrieve all members from this audience field programmatically? For instance, how to get their email addresses?
 

What an audience is composed of?
 
Target audience field can only contain 3 types of objects (see screenshot below):
  1. Global audiences (can be defined in the Shared Services Administration of the SharePoint central administration, Audiences section).
  2. Distribution/Security Groups (groups of your Active Directory).
  3. SharePoint Groups.

Audience selection


Let's code

1. Audience property

Once you filled-in target audience field , data are stored in "Audience" property of the underlying element (e.g. item, document, navigation node).

string audiencePropertyString = Convert.ToString(item.Properties["Audience"]);
Note that we use Convert.ToString() instead of .ToString() to avoid any null exception if the property does not exist.


2. Get audience IDs from audience property

AudienceManager class provides a very handy method GetAudienceIDsFromText that retrieves member's identifiers.
string[] globalAudienceIds;
string[] dlDistinguishedNames;
string[] sharePointGroupNames;

int result = AudienceManager.GetAudienceIDsFromText(
    audiencePropertyString,
    out globalAudienceIds,
    out dlDistinguishedNames,
    out sharePointGroupNames);

We now have three arrays fulfilled with audiences IDs.


3. Get users from global audiences

Once compiled, a global audience is just a list of users. Thus it's quite straightforward to enumerate them.

foreach (string globalAudienceId in globalAudienceIds)
{
    AudienceManager am = new AudienceManager(ServerContext.GetContext(SPContext.Current.Site));

    foreach (UserInfo member in am.GetAudience(new Guid(globalAudienceId)).GetMembership())
    {
        string email = member.Email;
        // TODO: what you need with this email ...
    }
}


4. Get emails of security groups members


In this example, we won't try to get users that are in groups of groups.

First this simple method helps us to get members of an Active Directory group:

/// <summary>
/// Get members of a specified AD group
/// </summary>
/// <param name="groupName">AD group name</param>
/// <returns>Members</returns>
private static List<DirectoryEntry> GetWindowsGroupMembers(string groupName)
{
    // Variables
    List<DirectoryEntry> members;
    DirectorySearcher ds;
    ResultPropertyCollection results;
    DirectoryEntry member;

    // Init
    members = new List<DirectoryEntry>();
    ds = new DirectorySearcher();

    // Init directory searcher filter
    if (groupName.StartsWith("cn=", StringComparison.InvariantCultureIgnoreCase))
    {
        ds.Filter = String.Format("({0})", groupName);
    }
    else
    {
        ds.Filter = String.Format("(cn={0})", groupName);
    
    }

    // Execute search
    results = ds.FindOne().Properties;

    // Get group members    foreach (Object memberColl in results["member"])
    {
        member = new DirectoryEntry(String.Format("LDAP://{0}", memberColl));

        if (member != null)
        {
            members.Add(member);
        }
    }

    // Return members list
    return members;
}


Then this method helps to get a property value of an active directory entry:

/// <summary>
/// Get property value from AD entry
/// </summary>
/// <param name="entry">Directory entry to request</param>
/// <param name="propertyName">Property name</param>
/// <returns>Property value</returns>
private static object GetWindowsUserPropertyValue(DirectoryEntry entry, string propertyName)
{
    // Variables
    object propertyValue = null;
    System.DirectoryServices.PropertyCollection userProps = null;

    // Get AD entry properties
    userProps = entry.Properties;
            
    // Get specified property values
    propertyValue = userProps[propertyName].Value;

    // Return property value
    return propertyValue;
}


Finally we get email adresses from security groups members:

foreach (string dlDistinguishedName in dlDistinguishedNames)
{
    // Connect to AD group
    DirectoryEntry group = new DirectoryEntry(String.Format("LDAP://{0}", dlDistinguishedName));
                
    // Init directory searcher
    DirectorySearcher ds = new DirectorySearcher(group);
                
    // Get AD group name
    string groupName = ds.FindOne().GetDirectoryEntry().Name;
    if (groupName.Contains(@"\"))
    {
        groupName = groupName.Substring(groupName.LastIndexOf(@"\") + 1);
    }

    // Get users infos
    foreach (DirectoryEntry member in GetWindowsGroupMembers(groupName))
    {
        string email = Convert.ToString(GetWindowsUserPropertyValue(member, "mail"));
        // TODO: what you need with this email ...
    }
}



5. Get users from SharePoint groups

A SharePoint group is composed of SharePoint users, but a SharePoint user can also be a security group!

foreach (string sharePointGroupName in sharePointGroupNames)
{
    // Get users from group
    foreach (SPUser user in SPContext.Current.Web.SiteGroups[sharePointGroupName].Users)
    {
        // Add simples users emails to list
        if (!user.IsDomainGroup)
        {
            string email = user.Email;
            // TODO: what you need with this email ...
        }
        // The user is an AD group reference
        else
        {
            // Get AD group names
            string groupName = null;
            if (user.LoginName.Contains(@"\"))
            {
                groupName = user.LoginName.Substring(user.LoginName.LastIndexOf(@"\") + 1);
            }
            else
            {
                groupName = user.LoginName;
            }

            // Get users infos and add email to list
            foreach (DirectoryEntry member in GetWindowsGroupMembers(groupName))
            {
                string email = Convert.ToString(GetWindowsUserPropertyValue(member, "mail"));
                // TODO: what you need with this email ...
            }
        }
    }
}


Important note

To be able to browse your Active Directory, the application pool identity must be a domain account. But this should be the case...


How to create custom categories for SharePoint diagnostic logging with IDiagnosticsManager and IDiagnosticsLevel

Download source code and sample (844 KB)
 
Goal of this post
 
I've been working on the past few days on a way to improve the logging infrastructure of our data visualization tool Charting for SharePoint.
 
I thought adding a new category to SharePoint Central Admin diagnostic logging screen would be straightforward. Damned! I'm still wondering why this part of the framework is so skeletal. I found so few documentation on IDiagnosticsManager and IDiagnosticsLevel - and the way to implement this stuff correctly - that I've decided to create a proxy class to make this finally easy.
 
Only very few lines of code are required to achieve that:
 
 
Play with the sample attached
 
The file attached to this post contains a sample implentation of my base class (see Samples folder from the Visual Studio solution)
 
You can try it by deploying Prexens.SharePoint.Diagnostics.wsp (see bin\Solution folder from the Visual Studio solution). This solution basically registers the DLL, provisions a farm level feature containing a receiver which registers the diagnostics manager when activated. The diagnostics manager is deleted when the feature is deactivated.
 
Implement your own diagnostics manager
 
Here are the steps to implement your own diagnostics manager based on my abstract class.
  1. Create a class library assembly and sign it. Add a new class and make it inherit from Prexens.SharePoint.Diagnostics.Samples.BaseDiagnosticsManager (see files attached to this post to get this class).

  2. Implement a parameterless constructor which passes a literal identifier for your diagnostics manager as a parameter of the base class contructor. Call RegisterCategory for any new entry you want to make visible from the list of SharePoint logging categories.   
    public SandboxDiagnosticsManager()
        : base("My diagnostics manager")
    {
        RegisterCategory("My first custom logging category");
        RegisterCategory("My second custom logging category");
    }
  3. Override the method GetCurrentOnFarm(SPFarm farm) and paste the code below. Simply replace SandboxDiagnosticsManager with the name of your own class.
    protected override BaseDiagnosticsManager GetCurrentOnFarm(SPFarm farm)
    {
        return farm.Services.GetValue<SandboxDiagnosticsManager>("My diagnostics manager");
    }
That's it, you diagnostics manager is ready to go. Event and trace severity levels will be automatically persisted to SharePoint config DB when farm admins tweak the event throttling of your custom categories. The SharePoint OO will give you a hook to a cached copy of these settings anywhere, anytime.
 
Next step is to register your diagnostics manager as a SharePoint service.
 
Register your custom diagnostics manager
 
Run the following code in a console app. Again, replace SandboxDiagnosticsManager with the name of your own class
SandboxDiagnosticsManager manager = new SandboxDiagnosticsManager();
SPFarm.Local.Services.Add(manager);
A cleaner way would be to create a farm feature registering your diagnostics manager on activation, and deleting it on deactivation (see attached file for a running sample of this approach).
 
Use your custom diagnostics manager
 
Event and trace severity levels associated with your custom categories can be retrevied this way:
SandboxDiagnosticsManager manager = 
SPFarm.Local.Services.GetValue<SandboxDiagnosticsManager>("My diagnostics manager");

IDiagnosticsLevel customLoggingCategory = manager.GetItem("My first custom logging category");

EventSeverity eventSeverity = customLoggingCategory.EventSeverity;
TraceSeverity traceSeverity = customLoggingCategory.TraceSeverity;
In my case, my custom categories are used in relevant part of the code of Charting for SharePoint to determine if a trace has to be outputted to ULS files according to trace and event severity levels configured through central admin.
 
Updates from my last post on March 6
I've updated my source code to make it Web farm compliant. Update method is indeed different when running code on several WFE.

SPDisposeCheck (SharePoint Dispose Checker Tool) helps you to follow "Best Practices: Using Disposable Windows SharePoint Services Objects"

Quick description of the tool (abstract from Microsoft SharePoint Team Blog)
 
 
SPDisposeCheck v1.3.1 has been released on MSDN Code Gallery, http://code.msdn.microsoft.com/SPDisposeCheck.  This tool will help improve the quality of your SharePoint assemblies.  It will inspect your SharePoint assemblies and check that you are correctly disposing of certain SharePoint objects (IDisposable objects which includes SPSite and SPWeb).  The tool is based upon the guidance published in this MSDN article, Best Practices: Using Disposable Windows SharePoint Services Objects
 
This tool is not supported by Microsoft and is recommended to be used on Developer workstations and not on production SharePoint Server installations.  For additional information, please head over to Paul Andrew's blog post.
 
How to use it
  1. Install SPDisposeCheck.msi on your development server.
  2. Open a command window.
  3. Type following command:
    "c:\Program Files\Microsoft\SharePoint Dispose Check\SPDisposeCheck.exe" <path to assemblies> -debug

How to associate a worklow to a list programmatically

Goal of this post

This post is a good example of how to programmatically associate a Workflow to a list.
The workflow can be standard or designed with SharePoint Designer or Visual Studio.
As a workflow is using a standard tasks list and a specific workflow history list to run properly, the code will check and use or create them if necessary.
It will also take care of associating the workflow with a list.

Code example

We assume that the variable web is a SPWeb object containing the list we want to associate the workflow with.

1. Declare objects we are going to use

SPList myList = null;                               // List to associate workflow to
string myListName = null;                           // My list name
SPList historyList = null;                          // Workflow history list
SPList taskList = null;                             // Workflow tasks list
string workflowTemplateGuid = null;                 // Workflow template Guid
SPWorkflowTemplate workflowTemplate = null;         // Workflow template
SPWorkflowAssociation workflowAssociation = null;   // Workflow association
string workflowAssocName = null;                    // Workflow association name

2. Init

Be sure to use the internal name of the list. And set the workflow association name, this will appair as workflow name in SharePoint list settings.
myListName = "My list name";
workflowAssocName = "My Workflow";

3. Get Workflow template

If you want to use a custom workflow and know its template GUID, use the GUID string.
workflowTemplateGuid = "BAD855B1-32CE-4bf1-A29E-463678304E1A";
workflowTemplate = web.WorkflowTemplates[new Guid(workflowTemplateGuid)];
Instead you can get the workflow template GUID by using the GetTemplateByName method.
workflowTemplate = web.WorkflowTemplates.GetTemplateByName(
"Template name",
System.Globalization.CultureInfo.CurrentCulture);

4. Get or create workflow history and tasks lists.

The history list is dedicated to workflows and it's based on the WorkflowHistory list template. Most of times you'll have to create it.
// Try to get workflow history list
try
{
      historyList = web.Lists["Workflow History"];
}
catch (ArgumentException exc)
{
      // Create workflow history list
      Guid listGuid = web.Lists.Add("Workflow History", "", SPListTemplateType.WorkflowHistory);
      historyList = web.Lists[listGuid];
      historyList.Hidden = true;
      historyList.Update();
}
The tasks list is a common tasks list based on Tasks list template. If you want to create a specific tasks list for the workflow, don't use "Tasks" title for it. In the example we want to use a dedicated tasks list and will name it "Workflow Tasks".
// Try to get workflow tasks list
try
{
      taskList = web.Lists["Workflow Tasks"];
}
catch (ArgumentException exc)
{
      // Create workflow tasks list
      Guid listGuid = web.Lists.Add("Workflow Tasks", "", SPListTemplateType.Tasks);
      taskList = web.Lists[listGuid];
      taskList.Hidden = true;
      taskList.Update();
}

5. Create workflow association, configure it and associate it to the list.

// Allow unsafe updates on web
web.AllowUnsafeUpdates = true;
try { // Create workflow association workflowAssociation = SPWorkflowAssociation.CreateListAssociation(
workflowTemplate,
workflowAssocName, taskList, historyList); // Set workflow parameters workflowAssociation.AllowManual = false; workflowAssociation.AutoStartCreate = true; workflowAssociation.AutoStartChange = false; // Add workflow association to my list myList.AddWorkflowAssociation(workflowAssociation); // Enable workflow workflowAssociation.Enabled = true; } finally { web.AllowUnsafeUpdates = false; }

Debug tips for SharePoint

Goal of this post
This post sums up all the different methods to get handy information when you need to debug a SharePoint piece of code.

Problematic overview
As a reminder, when an error occurs in a SharePoint page, the inexplicit error message appears:

Unknown error

Fortunately, you are able to display debug information, stack traces, detailed error messages, and more…


Solution, and many more

1. Set CustomError mode to Off and CallStack to true

When the message above occurs, the first step to execute is to configure the CustomError mode and the CallStack parameters in your web.config file:

  • Open the web.config file of your web application (e.g "C:\Inetpub\wwwroot\wss\VirtualDirectories\80\web.config")
  • Search for CustomError node, and set its mode attribute to Off if it was set to On:
    <customErrors mode="Off" />
    
  • Search for CallStack attribute of SafeMode node, and set it to true if it was set to false:
    <SafeMode MaxControls="200" CallStack="true" DirectFileDependencies="10" 
    TotalFileDependencies="50" AllowPageLevelTrace="false">
  • Save your changes and refresh your erroneous page:

System.Web.Compilation.AssemblyBuilder.Compile()
   at System.Web.Compilation.BuildProvidersCompiler.PerformBuild()
   at System.Web.Compilation.BuildManager.CompileWebFile(VirtualPath virtualPath)

You are now able to determine which instruction had caused the error and what is the real error message.

2. Display trace

When developing SharePoint applications or pages, even if the page builds successfully, you may need more information on the server variables, the headers, the forms post values, the cookies, the page web controls, the request trace, etc…

This feature natively exists in ASP.NET; you just have to enable it into your web application configuration and activate it on a specific page, or into the web.config file of your Web application:

  • First, open your web.config file (e.g "C:\Inetpub\wwwroot\wss\VirtualDirectories\80\web.config")
  • Search for AllowPageLevelTrace attribute of the SafeMode node (same line as CallStack attribute seen previously), and set it to true:
    <SafeMode MaxControls="200" CallStack="true" DirectFileDependencies="10" 
    TotalFileDependencies="50" AllowPageLevelTrace="true">

Allow trace on a specific page:

  • Open your buggy ASPX page, and add a trace attribute to page directives and set to true, for example:
    <%@ Page Language="C#" Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" 
    MasterPageFile="~/_layouts/application.master" Trace="true" %>
  • Save your changes, refresh your page, and scroll down, you'll see additional information:

Allow trace on the entire web site:

3. Using Visual Studio debugger

SharePoint developments can be debugged like console applications or windows applications by configuring the Visual Studio debugger.

Tip: you can attach your debugger to all the w3wp.exe processes, or determine which process is used by your current portal web application by opening a command prompt, and executing the following command:
%systemroot%\System32\cscript.exe %systemroot%\System32\iisapp.vbs

Once you have found the Application ID, you just have to attach your project to the right w3wp.exe process (the one that executes your current portal web application):

Visual Studio Debugger : Attach process

If you want to have more details about debugging tips within Visual Studio, you can find more documentation on MSDN:

Tip: when debugging a custom workflow, remember to select Workflow as code type when attaching the process. Otherwise, Visual Studio may crash without explicit error message.

4. Looking for LOG files

Depending on your diagnostic logging configuration (see Central Administration > Operations > Diagnostic logging), SharePoint can log events on text files located by default in: "C:\Program Files\Common files\Microsoft Shared\web server extensions\12\LOGS"

For instance, if you have to debug a custom Workflow developed with Visual Studio, open the last log file (by sorting them by date), place your cursor at the end of the file, and search for workflow infrastructure (direction: top). You'll see the detailed error that occurred while executing your workflow.

5. Write your own custom trace in SharePoint log files

Source code and usage sample can be found at the following MSDN url:

http://msdn.microsoft.com/hi-in/library/aa979522(en-us).aspx


How-to: Fix "Method 'post' of object 'IOWSPostData' failed" error

Goal of this post

This post aims at providing a step by step solution to an uncommon error which could happen when you try to import an Excel file to a SharePoint 2007 custom list.

Problematic overview

The "Import to a SharePoint List" feature is usually used to create and fill a custom list from an Excel source file.

For an unknown reason, this feature may not work properly on some client workstations whatever the version of SharePoint and Office Excel used. During the import step, the site designer receives the following error message box:


Solution

First we have to point out that this error isn't caused by SharePoint but by Ms Excel, and that the fix will have to be executed on each client workstation - at least site designers workstations - because standard users are not impacted by this error.

The issue will be fixed in two steps: first we need to edit the EXPTOOWS.XLA excel file, and then we have to restore the SharePoint sites collection(s).

Modify EXPTOOWS.XLA
  • Open the Excel add-in "EXPTOOWS.XLA" located by default in "c:\Program Files\Microsoft Office\Office12\<LCID>", where <LCID> is your language identifier (for example, 1033 for a standard English installation, 1036 for a French one).
  • Press ALT+F11 to open the Visual Basic source code editor
  • Comment out the following line : "lVer = Application.SharePointVersion(URL)" by adding a simple quote at the beginning of the line
  • Below the commented line, add the following instruction : IVer = 2
  • Save your changes, and restart the client machine.
Backup and restore site the sites collection

The Excel install is now fixed, and you will be able to import Excel files in custom SharePoint list on the new SharePoint site collections. But in order to make this solution work on the actual site collection, you will have to backup and restore it by using STSADM command line tool which is provided with SharePoint (more information on STSADM command line tool are available by following this link : http://technet.microsoft.com/en-us/library/cc261956.aspx)


Execute the following commands :

stsadm -o backup -url http://PORTAL-URL -filename c:\Backups\BACKUP-FILE.dat

stsadm -o restore -url http://PORTAL-URL -filename c:\Backups\BACKUP-FILE.dat -overwrite


You can now start again the custom list importation from an Excel file, it should now work properly.


How-to: insert a Data View Web Part in a WCM publication page

Goal of this post
This post guides you through the steps for inserting the famous DVWP, which is an exclusive part of SharePoint Designer, in a publishing page of a WCM site.

Problematic overview
As a reminder, when trying to open a publishing page with SharePoint Designer, the following message box appears:
 

Edit in browser

Because we can't open the page in SharePoint Designer, we cannot configure and add the DVWP…

Solution
SharePoint Designer contains a very handy feature for WCM scenarios: " Detach from Page Layout ". This will break the inheritance between the publishing content page and its page layout.

First, start by duplicating the publishing page:
 

Duplicate page


Right click on the copied page and select " Detach from Page Layout ": this will embed the layout in the content page.
We can now open the content page as a standard " collaborative " page, configure and insert our Data View Web Part:
 

Configure Data View Web Part


Once you've configured the Web Part, you can export it from the browser interface…
 

Export Web Part


… then re-import it in the SharePoint Web Part Gallery:
 

Upload to Web Part Gallery


You can now open the target content page again, and insert your custom DVWP directly in a Web Part zone.

Tip: we recommend you to keep the duplicated page in the library for further modifications!

How to debug custom STSADM commands

When developing custom STSADM commands, it could be handy to debug our code in the real execution context of STSADM. To do so, you only have to deal with your Visual Studio project configuration.
 
1. Right click your VS project and go to properties
 
2. From Build Events tab, paste the following commands into the Post-buid event command line box
 
"%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /uf $(TargetName)
"%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" /if "$(TargetPath)"
 
3. From Debug tab, choose Start external program as a Start Action and browse for file C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\STSADM.EXE
 
 
4. In the Command line arguments text box, specify -o <name of your command> and the rest of your input parameters
 
 
5. You can now add break points to your code, run your project and debug your code in real conditions

1 - 10 Next
Categories
Links
  Prexens Web Site
  Charting for SharePoint
  Charting for SharePoint (Codeplex)
  Charting for SharePoint Starter kit

Microsoft Certified Partner