19Jun

Um in Sharepoint anfragen auf eigene ASPX Seiten umzuleiten, besteht die Möglichkeit dies durch die Verwendung eines „IHttpModules“ zu realisieren. Das Modul greift spezifische Anfragen auf definierte Seiten ab und leitet diese auf definierte Seiten um.

Hier der Code, um eine Anfrage auf die CreateWebPage.aspx auf die MyPage.aspx umzuleiten:

pic1

Die Verwendung des IHttpModule, welches durch einen Eintrag in die „web.config“ realisiert wird, kann über ein Feature mit Hilfe des SPWebConfigModification Objektes erreicht werden. Diese Feature setzt und löscht automatisch den notwendigen Eintrag in der „web.config“.

Hier der Beispielcode um das oben gezeigte IHttpModule anzubinden:

pic2

pic3

Wichtig: Um auch die “Remove” Methode korrekt ausführen zu können, müssen die Eigenschaften des SPWebConfigModification Objektes richtig gewählt werden.

1. Die Eigenschaft “EnsureChildNote” muss angewandt werden, da mit der EnsureSection eine spätere “Remove” Methode keine Auswirkung liefert.

mod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;

2. Der “mod.Name” muss entsprechend der “mod.Values” angepasst werden. Wird der Name des Objektes willkürlich gewählt, ist die Funktionalität der “Add” Methode gewährleistet, doch wird die Funktionalität der “Remove” Methode kein Resultat liefern.

entryvalue = @”<add name=”"IHttpModule”" type=”"namespace.class, assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=…”" />”;

Der Angepasste “mod.Name” muss dann folgend lauten:

entryname = “add[@name='IHttpModule'][@type='namespace.class, assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=...]“;

Hier nun noch der Inhalt der “feature.xml”. Zu beachten ist, dass der Scope auf der WebApplication liegen muss.

pic4

Links zum Thema:

http://blogs.technet.com/tatianasv/rss_tag_SharePoint+development.xml

http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spwebconfigmodification.aspx

http://blog.tedpattison.net/Lists/Posts/Post.aspx?ID=4

MFG Gordon

Technorati Tags: ,

21Apr

Gelegentlich werden in Spalten von ListItems auch Benutzerinformationen gespeichert - als Beispiel sollen hier “erstellt von” und “geändert von” dienen. Die zugehörigen Felder des Items beinhalten meist Zeichenketten im Format <BenutzerID>;#<Benutzername>.

Bevor man sich nun in String-Operationen versucht - z.B. durch Split an der Stelle “;#” und Erzeugung der Objekte über die extrahierte ID - sollte man sich mit den Klassen SPFieldUser bzw. SPFieldUserValue auseinandersetzen, die hier viel Arbeit und Fehleranfälligkeit abnehmen können, da hier sehr einfach Objekte vom Typ SPUser bzw. SPGroup zurückgegeben werden können.

Im Folgenden 2 beispielhafte Methoden, die den Zugriff verdeutlichen sollen:

private SPUser GetSPUser(SPListItem item, Guid fieldid)
{
    SPUser retval = null; 

    #region check params
    if (item == null)
    {
        throw new ArgumentNullException("item");
    }
    if (fieldid == Guid.Empty)
    {
        throw new ArgumentException("Empty Guid is not allowed.", "fieldid");
    }
    #endregion 

    try
    {
        SPFieldUser field = item.Fields[fieldid] as SPFieldUser;
        if (field != null)
        {
            SPFieldUserValue fieldValue = field.GetFieldValue(item[fieldid].ToString()) as SPFieldUserValue;
            if (fieldValue != null)
            {
                retval = fieldValue.User;
            }
        }
    }
    catch (Exception ex)
    {
        throw ex;
    } 

    return retval;
}
private SPGroup GetSPGroup(SPListItem item, Guid fieldid)
{
    SPGroup retval = null; 

    #region check params
    if (item == null)
    {
        throw new ArgumentNullException("item");
    }
    if (fieldid == Guid.Empty)
    {
        throw new ArgumentException("Empty Guid is not allowed.", "fieldid");
    }
    #endregion 

    try
    {
        SPFieldUser field = item.Fields[fieldid] as SPFieldUser;
        if (field != null)
        {
            SPFieldUserValue fieldValue = field.GetFieldValue(item[fieldid].ToString()) as SPFieldUserValue;
            if (fieldValue != null)
            {
                string groupName = fieldValue.LookupValue;
                retval = item.Web.SiteGroups[groupName];
            }
        }
    }
    catch (Exception ex)
    {
        throw ex;
    } 

    return retval;
}

Der Aufruf ist nun recht trivial:

SPUser modifiedbyuser = GetSPUser(tasklistitem, SPBuiltInFieldId.Editor);
DateTime modifiedat = Convert.ToDateTime(tasklistitem[SPBuiltInFieldId.Modified]);
SPUser assignedto = GetSPUser(tasklistitem, SPBuiltInFieldId.AssignedTo);
Technorati Tags: , ,

17Apr

Bei der Entwicklung komplexer Workflows für den SharePoint spielen auch Workflowaufgaben für die Benutzerinteraktion während der Ausführung des Workflows eine nicht unbedeutende Rolle.

Eine solche Aufgabe hat nicht nur die Standardeigenschaften wie einen Titel oder eine Beschreibung sondern auch erweiterte Eigenschaften. Das praktische daran ist, dass diese kein eigenes Feld in der Liste benötigen sondern die Daten in den bereits vorhandenen Feldern gespeichert werden.

Und wenn man weiß, wie es geht, ist der Zugriff auf diese Daten auch sehr einfach.

1.) bei der Erstellung
Während der Erstellung der Aufgaben verwendet man in der Regel den Typ WssTaskActivity. Dieser verfügt bereits über eine Eigenschaft ExtendedProperties, die vom Typ Hashtable ist und in die man seine gewünschten Daten schreiben kann.

createTask.TaskId = Guid.NewGuid();
createTask.TaskProperties.Title = "Bitte genehmigen";
createTask.TaskProperties.ExtendedProperties["AdditionalText"]
    = "Bitte persönlich bearbeiten!";

2.) über den Item der TaskList

Möchte man nun später noch einmal darauf zugreifen, sieht man sich vor die Aufgaben gestellt, die Daten wieder aus dem SPListItem der TaskList zu lesen und nach der Änderung auch wieder zu schreiben. Sieht man sich das SPListItem genauer an, das die Aufgabe repräsentiert, so erkennt man, dass es ein Feld ExtendedProperties enthält, in dem die Daten auch enthalten sind - allerdings sind hier einfach die XML-Attribute die die Eigenschaften repräsentieren angegeben:

"AdditionalText='Bitte persönlich bearbeiten!' Department='Einkauf'"

Die große Frage ist jetzt, wie man einfach auf diese Daten zugreift, diese verändert und wieder abspeichert. Die Hashtable die bei der Erstellung wäre hierfür ein adäquates Objekt, das hier aber wie es zunächst scheint nicht vorhanden ist. Aber man muss hier nicht verzagen - auch dafür gibt es eine bereits vorhandene Lösung, auf die man aber erst kommen muss. Der Typ SPWorkflowTask aus dem NameSpace Microsoft.SharePoint.Workflow verfügt über 2 interessante statische Methoden: GetExtendedPropertiesAsHashtable hat als Rückgabewert genau die eben noch vermisste Hashtable und AlterTask verfügt über die Möglichkeiten, den Item mit den geänderten Daten auch wieder zu aktualisieren.

// get the extended properties hashtable
Hashtable taskItemExtProps = SPWorkflowTask.GetExtendedPropertiesAsHashtable(this.TaskListItem);
// write new values to the hashtable
taskItemExtProps["AdditionalText"] = "Mein Kommentar: genehmigt!";
// update the task item with new values
SPWorkflowTask.AlterTask(this.TaskListItem, taskItemExtProps, true);
Technorati Tags: , , , , ,

27Feb

Als netter ASP.Net Programmierer mutet man dem Anwender wegen eines Klicks in eine CheckBox (in diesem Fall als Item einer CheckBoxList) kein Postback/Reload der Seite zu. Nun hat man ja aber vielleicht doch den Wunsch, das eine oder andere beim Klick zu erledigen - clientseitig per Javascript. Wenn möglich, will man den einzelnen CheckBoxList Items noch ein paar Attribute mitgeben, die in der Javascript-Funktion ausgewertet werden können und im Idealfall weiß die Javascript-Funktion auch gleich, welcher Item geklickt wurde. Das sollte eigentlich kein Problem sein - denkt man.

Also versucht man es erstmal ganz einfach mit folgendem Codeschnipsel im C# CodeBehind der Seite:

image

-> keine Reaktion :-(

Das gleiche noch einmal mit “onclick” anstelle “onchange” -> selbes Ergebnis :-(

Nun hilft alles nichts - man fängt an nachzudenken… Ein Blick in den Seitenquellcode ist ziemlich aufschlussreich: Die CheckBoxList Items haben einen <span> um den eigentlichen <input>-Tag des Items stehen - und in diesem landen unsere Attribute:

image

So funktioniert das also leider nicht. Ein kurzes Googlen zeigt nicht nur, dass andere das Problem auch schon hatten, sondern auch eine Lösung: Das “onclick” darf nicht an die einzelnen Items, sondern muss an die CheckBoxList gebunden werden:

image

Welcher Item geklickt wurde, kann man nun leider nicht mehr einfach an die Javascript-Funktion übergeben. Dies muss man über eine for-Schleife herausfinden. Die Attribute für die einzelnen Items können aber mit einem kleinen Trick trotzdem in der Javascript-Funktion abgefragt werden: Man legt sich zusätzlich zum Array mit den CheckBoxList Items (<input>-Tags) noch ein Array für die Attribute (<span>-Tags) an, das natürlich die gleiche Länge hat und über den gleichen Index abgefragt werden kann.

Alles in allem sieht die Javascript-Funktion in der Seite dann so aus:

function CheckboxChanged()
{
var checkBoxList = document.getElementById(’<%= SampleCheckBoxList.ClientID %>’);
//Array für die CheckBoxList Items
var checkboxes = checkBoxList.getElementsByTagName(’input’);
//Array für die Attribute der CheckBoxList Items
var checkboxAttributes = checkBoxList.getElementsByTagName(’span’);
for (var i=0; i<checkboxes.length;i++)
{
alert(checkboxes[i].checked);
alert(checkboxAttributes[i].code);
alert(checkboxAttributes[i].text);
}
}

Und der C# CodeBehind für das Hinzufügen des “onclick” und der Attribute so:

//set attributes for the items of SampleCheckBoxList (reqired for javascript function)
foreach (ListItem item in SampleCheckBoxList.Items)
{
item.Attributes.Add(”code”, item.Value);
item.Attributes.Add(”text”, item.Text);
}
//set the javascript function to be called at a checkbox item click
SampleCheckBoxList.Attributes.Add(”onclick”, “CheckboxChanged()”);

Das sollte wirklich kein Problem sein… :-)

Technorati Tags: , , , ,

26Nov

Das Programm STSADM ist ein sehr nützliches Tool für häufig wiederkehrende Aufgaben auf einem Sharepoint-Server. Einer der großen Vorteile ist es, dass man diese Aufgaben per Kommandozeile und damit z.B. per Batch abarbeiten kann.

An manchen Stellen kommt aber auch der dieses Programm an seine Grenzen. Glücklicherweise kann der beherzte Entwickler hier selbst eingreifen und sich eigene Erweiterungen schreiben, die genau den Bedarfsfall abbilden, der gerade vorliegt.

Die Entwicklung einer eigenen Erweiterung ist gar nicht so kompliziert, wie es sich anhört. Dazu sind folgende Schritte notwendig:

Zunächst erstellt man sich ein neues DLL-Projekt und verlinkt die Microsoft.Sharepoint-Assembly. Jede Klasse, die das ISPStsadmCommand-Interface implementiert, kann als Erweiterung fungieren.

new StsadmCommand

Das Interface beinhaltet 2 Funktionen. Einerseits GetHelpMessage - diese Funktion wird dann aufgerufen, wenn man stsadm -help <befehl> aufruft; andererseits  Run - diese Funktion wird aufgerufen, wenn man stsadm -o <befehl> in der Kommandozeile eingibt.

Implementation ISPStsadmCommand 

Beide Methoden beinhalten einen Parameter command. Dieser beinhaltet zur Ausführungszeit den Befehl - vermutlich aus dem Grund, da eine implementierende Klasse auf mehrere Befehle gemappt werden kann. Das Mapping selbst ist recht einfach. Dazu erzeugt man sich eine neue XML-Datei mit dem Namen stsadmcommands.<eigenername>.xml, die später unter “Gemeinsame Dateien\Microsoft Shared\web server extensions\12\CONFIG” abgelegt wird.

Im Beispiel wird das command copynewfiles auf die eben erstellte Klasse CopyFiles gemappt.

stsadmconfig

Nachdem die DLL nun in den GAC deployed ist, kann der Aufruf erfolgen.

output


09Nov

Damit man gezielt versionsbezogenen Support leisten kann, muss der Kunde die aktuelle Version z.B. eines SharePoint Webparts mitteilen können.

Es bietet sich an, dafür die Version der Assembly zu verwenden. Per Default ist diese jedoch auf [assembly: AssemblyVersion("1.0.0.0")] eingestellt und ändert sich nicht von allein. Man kann durch Einsatz von Sternchen diese Versionierung automatisieren. Visual Studio zählt dann von selbst hoch. (Mehr Informationen: AssemblyVersionAttribute-Klasse)

Für schnellen und gezielten Support wäre es aber besser, wenn der Kunde die Subversion Revisionsnummer des Quellcodes nennen könnte.

Dazu sind folgende Schritte notwendig:

  1. In der AssemblyInfo.cs den Eintrag
    [assembly: AssemblyDescription("")]
    zu
    [assembly: AssemblyDescription("$Rev$ $Author$ $LastChangedDate$")]
    ändern.
  2. Im SVN-Client die Property “svn:keywords” für die AssemblyInfo.cs anlegen und den Wert auf “Rev Author LastChangedDate” setzen.

    Subversion Property

  3. Nun setzt Subversion zukünftig immer die aktuellen Werte bei Commit der AssemblyInfo.cs ein.

    Achtung: Die Ersetzung in der AssemblyInfo.cs findet wirklich nur bei Commit genau dieser Datei statt. Ändert sie sich nicht, sondern wurden z.B. nur Änderungen an anderen Dateien vorgenommen, so wird auch nichts ersetzt.

    Dieses Problem lässt sich wie folgt lösen:

    1. Vor jedem Release sollte vom Entwickler der Wert von AssemblyVersion nach einem festgelegtem Muster erhöht werden. Z.B. erster Teil “bei neuer Hauptversion”, zweiter Teil “bei Erweiterungen”, dritter Teil “bei Bugfixes”, vierter Teil “immer 0″.So hat man zusätzlich zu der eher maschinennahen Revisionsnummer auch noch eine menschenlesbare Nummer, an der man schnell Umfang und Art der Änderungen ablesen kann.

      oder

    2. Ist auf dem Entwickler-Rechner TortoiseSVN installiert, so kann man dessen Kommandozeilentool SubWCRev.exe verwenden. Dann muss nicht erst eine manuelle Änderung in der AssemblyInfo.cs vorgenommen werden.

      Dazu kopiert man die AssemblyInfo.cs zu AssemblyInfo.cs.Template, ändere den Platzhalter in der AssemblyInfo.cs.Template von
      [assembly: AssemblyDescription("$Rev$ $Author$ $LastChangedDate$")]
      zu
      [assembly: AssemblyDescription("$WCREV$ $WCDATE$")]
      und rufe im Pre-Build Event das Tool wie folgt auf:

      Pre-Build

      C:\Program Files\TortoiseSVN\bin\SubWCRev.exe $(SolutionDir) $(ProjectDir)\Properties\AssemblyInfo.cs.Template $(ProjectDir)\Properties\AssemblyInfo.cs

      Wichtig: Man man muss hier mit einem Template arbeiten, da sonst die Ersetzung nur beim ersten Mal funktioniert.

      Jetzt hat man bei jedem Build eine aktuelle AssemblyInfo.cs ohne das man erst jedes Mal ein Commit ausführen muss.

    Da im Gegensatz zu CVS bei SVN die Revisionsnummer nicht für jede Datei einzeln erhöht wird, sondern für das gesamte Repository, haben wir mit der Revisionsnummer eine optimale Angabe um den Versionsstand des Kunden nachzustellen. Der Kunde kann die Version nun in der gelieferten DLL ablesen. (Rechte Maustaste auf Eigenschaften…)

    DLL properties

  4. Um dem Kunden das Ablesen zu vereinfachen, zeigt man die Information aber besser im Impressum der Anwendung an, führt sie in Logfiles mit, etc. So funktioniert das Auslesen mit C#:

    Versionsnummer der Assembly:
    Assembly.GetExecutingAssembly().GetName().Version

    Subversion-Informationen:
    string svnInfo = string.Empty;
    object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyDescriptionAttribute), true);
    if ( attributes.Length > 0 ) {
    AssemblyDescriptionAttribute descriptionAttribute = attributes[0] as AssemblyDescriptionAttribute ;
    svnInfo = descriptionAttribute.Description;
    }

Nun geht’s schneller beim nächsten Bug… ;-)

Technorati Tags: ,