Passaggio all'ora solare 25 ottobre 2020 03:0002:00 sposta indietro l'orologio di 1 ora (si dorme 1 ora in più)
L'esempio che segue illustra come creare una Custom Activity e Custom Condition per il workflow da usare con SharePoint Designer 2007.
In questo caso la custom activity permette di creare un sito basato su un determinato template specificando: titolo, nome, descrizione e titolo template (o nome template).

I passi da seguire sono:
  • creare una classe che eredita da System.Workflow.ComponentModel.Activity
  • creare un file xml che descrive la activity, da posizionare in \12\TEMPLATE\1033\Workflow\<nomeFile>.ACTIONS
  • registrare la dll come safe control nel web.config (configuration\SharePoint\SafeControls\SafeControl)
  • aggiungere la dll nel web.config nella posizione configuration\System.Workflow.ComponentModel.WorkflowCompiler\authorizedTypes\authorizedType
  • andare nella central administration ed attivare la feature sulla web application (http://<serverUrl>:<port>/_admin/ManageWebAppFeatures.aspx)

La classe che gestisce la custom activity (AddSPWeb.cs)
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WorkflowActions;

namespace Sgart.SharePoint.WF
{
    /// <summary>
    /// add a new site (SPWeb)
    /// </summary>
    public class AddSPWeb : System.Workflow.ComponentModel.Activity
    {
        #region Constructors
        // Methods
        public AddSPWeb()
        {
            this.InitializeComponent();
        }
        #endregion

        #region Initialize

        private void InitializeComponent()
        {
            base.Name = "AddSPWeb";
            base.Description = "AddSPWeb";
        }

        #endregion

        #region Properties

        [Description("Workflow Context")]
        [Category("Custom")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [ValidationOption(ValidationOption.Required)]
        public WorkflowContext __Context
        {
            get
            {
                return ((WorkflowContext)(base.GetValue(__ContextProperty)));
            }
            set
            {
                base.SetValue(__ContextProperty, value);
            }
        }
        public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(AddSPWeb));


        [Description("ListItem")]
        [Category("Custom")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [ValidationOption(ValidationOption.Required)]
        public int __ListItem
        {
            get
            {
                return ((int)(base.GetValue(__ListItemProperty)));
            }
            set
            {
                base.SetValue(__ListItemProperty, value);
            }
        }
        public static DependencyProperty __ListItemProperty = DependencyProperty.Register("__ListItem", typeof(int), typeof(AddSPWeb));

        [Description("Id of the list")]
        [Category("Custom")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [ValidationOption(ValidationOption.Required)]
        public string __ListId
        {
            get
            {
                return ((string)(base.GetValue(__ListIdProperty)));
            }
            set
            {
                base.SetValue(__ListIdProperty, value);
            }
        }
        public static DependencyProperty __ListIdProperty = DependencyProperty.Register("__ListId", typeof(string), typeof(AddSPWeb));


        [Description("Title of the site to be created")]
        [Category("Custom")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [ValidationOption(ValidationOption.Required)]

        public string SiteTitleField
        {
            get
            {
                return ((string)(base.GetValue(SiteTitleFieldProperty)));
            }
            set
            {
                base.SetValue(SiteTitleFieldProperty, value);
            }
        }
        public static DependencyProperty SiteTitleFieldProperty = DependencyProperty.Register("SiteTitleField", typeof(string), typeof(AddSPWeb));

        [Description("Name of the site to be created")]
        [Category("Custom")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [ValidationOption(ValidationOption.Required)]
        public string SiteNameField
        {
            get
            {
                return ((string)(base.GetValue(SiteNameFieldProperty)));
            }
            set
            {
                base.SetValue(SiteNameFieldProperty, value);
            }
        }
        public static DependencyProperty SiteNameFieldProperty = DependencyProperty.Register("SiteNameField", typeof(string), typeof(AddSPWeb));

        [Description("Description of the site to be created")]
        [Category("Custom")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [ValidationOption(ValidationOption.Required)]
        public string SiteDescriptionField
        {
            get
            {
                return ((string)(base.GetValue(SiteDescriptionFieldProperty)));
            }
            set
            {
                base.SetValue(SiteDescriptionFieldProperty, value);
            }
        }
        public static DependencyProperty SiteDescriptionFieldProperty = DependencyProperty.Register("SiteDescriptionField", typeof(string), typeof(AddSPWeb));

        [Description("Name of the template")]
        [Category("Custom")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [ValidationOption(ValidationOption.Required)]
        public string TemplateName
        {
            get
            {
                return ((string)(base.GetValue(TemplateNameProperty)));
            }
            set
            {
                base.SetValue(TemplateNameProperty, value);
            }
        }
        public static DependencyProperty TemplateNameProperty = DependencyProperty.Register("TemplateName", typeof(string), typeof(AddSPWeb));

        #endregion

        #region Activity

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            //get current web and language
            SPWeb webCurrent = __Context.Web;
            uint lcid = webCurrent.Language;
            
            //get selected template
            SPWebTemplateCollection templates = __Context.Site.GetWebTemplates(lcid);
            SPWebTemplate template = null;
            try
            {
                //try with internal template Name (STS#1)
                template = templates[this.TemplateName];
            }
            catch
            {
                //else search template by Title (Blank Site)
                foreach (SPWebTemplate wt in templates)
                {
                    if (wt.IsHidden == false 
                        && wt.Title.Equals(this.TemplateName, StringComparison.InvariantCultureIgnoreCase) == true)
                    {
                        template = wt;
                        break;
                    }
                }
            }

            // Create the new site
            SPWeb newWeb = webCurrent.Webs.Add(this.SiteNameField
                , this.SiteTitleField, this.SiteDescriptionField
                , template.Lcid, template, false, false);

            // This activity has finished its job.
            return ActivityExecutionStatus.Closed;
        }

        #endregion
    }
}
Da notare come le property vengono bindate alla descrizione presente nel file xml e il metodo Execute che implementa la custom activity (DependencyProperty).

Il file xml di configurazione (Sgart.SharePoint.WF.ACTIONS)
<?xml version="1.0" encoding="utf-8"?>
<WorkflowInfo Language="en-us">
  <Actions Sequential="then" Parallel="and">
    <Action Name="Sgart - Create Web"
		  ClassName="Sgart.SharePoint.WF.AddSPWeb"
		  Assembly="Sgart.SharePoint.WF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=53a6df0e63086949"
		  AppliesTo="list"
		  Category="Sgart"
		  UsesCurrentItem="true">
      <RuleDesigner Sentence="Create a new site called %1 by template %4 at url %2 with description %3">
        <FieldBind Field="SiteTitleField" Text="Site Title" Id="1" DesignerType="TextArea"/>
        <FieldBind Field="SiteNameField" Text="Site Name" Id="2" DesignerType="TextArea"/>
        <FieldBind Field="SiteDescriptionField" Text="Site Description" Id="3" DesignerType="TextArea"/>
        <FieldBind Field="TemplateName" Text="Template Name" Id="4" DesignerType="TextArea"/>
      </RuleDesigner>
      <Parameters>
        <Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" />
        <Parameter Name="__ListId" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="__ListItem" Type="System.Int32, mscorlib" Direction="In" />
        <Parameter Name="SiteTitleField" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="SiteNameField" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="SiteDescriptionField" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="TemplateName" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Action>
  </Actions>
</WorkflowInfo>
Dove Action descrive quale classe implementa la custom activity, RuleDesigne indica a SharePoint Designer come disegnare l'activity e Parameter descrive i parametri della activity. In particolare i primi tre parametri (__Context, __ListId e __ListItem) sono gestiti in automatico dal workflow.

La riga per registrare la dll come safe (web.config)
      <SafeControl Assembly="Sgart.SharePoint.WF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=53a6df0e63086949" 
                   Namespace="Sgart.SharePoint.WF" TypeName="*" Safe="True" />

La riga per registrare la dll come activity del workflow (web.config)
      <authorizedType Assembly="Sgart.SharePoint.WF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=53a6df0e63086949" 
                      Namespace="Sgart.SharePoint.WF" TypeName="*" Authorized="True" />

Attivarlo nella web application interessata. A questo punto è possibile utilizzare la nuova activity in SharePoint Designer 2007.


Se si vuole creare anche una custom condition, i passi sono i seguenti:
  • creare un metodo che ritorna un boolean
  • aggiungere la descrizione della condizione al file xml (Sgart.SharePoint.WF.ACTIONS)

I metodi IF per le conditions (IfSPWeb.cs)
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WorkflowActions;

namespace Sgart.SharePoint.WF
{
    public class IfSPWeb
    {

        public static bool IfNotExistSPWebWithTitle(WorkflowContext context, string listId, int listItem, string title)
        {
            bool ok = true;
            SPWeb webCurrent = context.Web;
            foreach (SPWeb web in webCurrent.Webs)
            {
                try
                {
                    if (web.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase) == true)
                    {
                        ok = false;
                        break;
                    }
                }
                catch{}
                finally
                {
                    web.Dispose();
                }
            }
            return ok;
        }

        public static bool IfNotExistSPWebWithName(WorkflowContext context, string listId, int listItem, string name)
        {
            bool ok = true;
            SPWeb webCurrent = context.Web;
            string[] names = webCurrent.Webs.Names;
            foreach (string n in names)
            {
                if (n.Equals(name, StringComparison.InvariantCultureIgnoreCase) == true)
                {
                    ok = false;
                    break;
                }
            }
            return ok;
        }
    }
}

e il relativo file xml
<WorkflowInfo Language="en-us">
  <Conditions And="and" Or="or" Not="not" When="If" Else="Else if">
    <Condition Name="Sgart - Check if NOT exist site with Title"
       FunctionName="IfNotExistSPWebWithTitle"
		  ClassName="Sgart.SharePoint.WF.IfSPWeb"
		  Assembly="Sgart.SharePoint.WF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=53a6df0e63086949"
       AppliesTo="list"
       UsesCurrentItem="true">
      <RuleDesigner Sentence="not exist site with title %1">
        <FieldBind Field="_1_" Text="Site Title" Id="1" DesignerType="TextArea"/>
      </RuleDesigner>
      <Parameters>
        <Parameter Name="_1_" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Condition>
    <Condition Name="Sgart - Check if NOT exist site with Name"
       FunctionName="IfNotExistSPWebWithName"
		  ClassName="Sgart.SharePoint.WF.IfSPWeb"
		  Assembly="Sgart.SharePoint.WF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=53a6df0e63086949"
       AppliesTo="list"
       UsesCurrentItem="true">
      <RuleDesigner Sentence="not exist site with name %1">
        <FieldBind Field="_1_" Text="Site Name" Id="1" DesignerType="TextArea"/>
      </RuleDesigner>
      <Parameters>
        <Parameter Name="_1_" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Condition>
  </Conditions>
</WorkflowInfo>
In questo caso notare il riferimento hai parametri posizionale (Filed="_1_") e non tramite nome.


Nel file sgart-sharepoint-wf.zip c'è sia il codice che il file di installazione (setup.exe) con i file xml sia in italiano (1040) che in inglese (1033).

Se si apportano modifiche, compilare il progetto, eseguire MakeSolution.bat per ricreare il file di solution (WSP), installarlo con setup.exe e attivare la feature nella web application.

Per usare la nuova Custom Activity in SharePoint Designer 2007:
  • dal designer aprire il sito
  • andare in File \ New
  • selezionare SharePoint Content
  • scegliere workflow e premere ok
  • dare un nome al workflow, selezionare una lista e scegliere una modalità di avvio
  • premere next e selezionare l'action Sgart - Crate Web
  • compilare tutti i campi richiesti dall'activity e premere finish
il workflow è stato creato ed agganciato alla lista selezionata.


The list of workflow actions on the server references an assembly that does not exist. Some actions will not be available. The assembly strong name is [...]. Contact your server administrator for more information.
Se ti compare questo errore nel disigner, il motivo sembra scontato, ovvero non viene trovato l'assembly... ma non è così. In realtà il messaggio è da interpretate come "c'è qualche cosa che non va quando il workflow cerca di instanziare la classe", questo qualche cosa può essere:
  • l'assembly non è registrato nel web.config come sicuro (strong)
  • il costruttore non va bene
  • l'xml di definizione non è corretto
  • una proprietà (DependencyProperty ) della classe non è corettamente definita (ad esempio il parametro dell'ultimo typeof non si riferisce alla classe corrente)
  • e... qualunque altro errore :-(
in poche parole non abbiamo nessuna indicazione significativa da questo errore, non viene registrato niente nei log e anche andando in debug non si ha alcuna informazione sulla posizione effettiva dell'errore... non era meglio mettere un errore del tipo "non so che c...o sta succedendo!" almeno uno vaglia tutte le possibilità e non si fissa nella direzione sbagliata.