Beiträge zum Thema ‘Microsoft Sharepoint’

Liebe Leser, in diesem Bereich finden Sie Tipps und Tricks rund um die Microsoft Sharepoint Technologie.

19Jun

… oder der lange Weg zum Glück

Manchmal hat man als Sharepoint-Entwickler das Bedürfnis, sich in einer benutzerdefinierten Liste an das Hinzufügen oder Ändern eines Items anzuhängen und die Aktion abzubrechen und dabei eine nette benutzerdefinierte Fehlermeldung ausgeben. Das sollte eigentlich überhaupt kein Problem sein, man erstellt einfach einen EventReceiver für ItemAdding bzw. ItemUpdating und setzt dort die ErrorMessage sowie die Cancel Property:

image

Nun erscheint allerdings leider nicht wie erwartet die benutzerdefinierte Fehlermeldung, sondern stattdessen eine böse Exception (DataFormWebPartException “The data source control failed to execute the insert command”):

clip_image002

Google verhilft uns zur Erkenntnis, dass dies ein Known Bug in WSS 3.0 ist (wird auch durch WSS 3.0 Service Pack 1 nicht behoben). Der Bug tritt offenbar nur in benutzerdefinierten Listen auf. Für dieses Problem ist ein Hotfix verfügbar. Der zugehörige Knowledge Base Artikel incl. Möglichkeit zum Anfordern des Hotfixes findet sich unter http://support.microsoft.com/default.aspx?scid=kb;en-us;949749

Aber Vorsicht! Nachdem man die Hürde genommen hat, die Mail mit dem Hotfix zu erhalten (Tipp: unter Junk E-Mail nachschauen) und frohgemut den Hotfix installiert hat (wobei man natürlich als gelernter Microsoft-Entwickler auch das iisreset nicht vergessen hat), geht hinterher auf dem Sharepoint Server erstmal gar nichts mehr:

image

Abhilfe schafft die Ausführung des Sharepoint Products and Technologies Configuration Wizard. Nach der Ausführung funktioniert nicht nur unser Sharepoint wieder, sondern nun wartet der EventReceiver auch mit unserer benutzerdefinierten Fehlermeldung auf:

image


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: , , , , ,

26Mrz

Die Standard-MasterPage von SharePoint (default.master) wurde ohne Angabe eines DOCTYPE geschrieben. Dadurch fällt der IE beim Rendern in den Quirks Mode zurück. Entsprechend wurden SharePoint-eigene Funktionalitäten wie das Verschieben eines WebParts auf Grundlage des Quirks Mode geschrieben.

Wenn man sich nun aber an Standards halten, und einen DOCTYPE angeben möchte, bekommt man u.U. Probleme in SharePoint.

So bringt zum Beispiel des Einfügen der Angabe

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

einen Fehler beim Ausführen der JavaScript-Methode “MSOLayout_GetRealOffset” hervor, wenn sich der WebPart in einem relativ positionierten DIV befindet:

Message: Objekt erforderlich
Line: 1572
Char: 3
Code: 0
URI: http://SERVER/_layouts/1031/ie55up.js?rev=Ni7%2Fj2ZV%2FzCvd09XYSSWvA%3D%3D

Mögliche Lösungen für dieses Problem sind:

1. Anpassen der JavaScript-Methode

Dies sollte jedoch nicht direkt in der <12>\TEMPLATE\LAYOUTS\1033\IE55UP.JS geschehen, da es dort durch Updates überschrieben werden kann. Und spätestens beim Deployen auf einer Farm wird es umständlich.

Daher sollte man die Anpassung lieber in seiner Masterpage vornehmen. Dazu überschreibt man einfach die “fehlerhafte” Methode indem man nach SPWebPartManager folgenden Code einfügt:

<script language="javascript" type="text/javascript">
   1: 
   2:     function MSOLayout_GetRealOffset(StartingObject, OffsetType, EndParent)
   3:     {
   4:         var realValue=0;
   5:         if (!EndParent) EndParent=document.body;
   6:         for (var currentObject=StartingObject; currentObject && currentObject !=EndParent && currentObject != document.body; currentObject=currentObject.offsetParent)
   7:         {             var offset = eval('currentObject.offset'+OffsetType);
   8:             if (offset) realValue+=offset;
   9:         }
  10:         return realValue;
  11:     }
</script>

oder 2. Vermeiden von position:relative bei Containern für WebParts

Stattdessen kann man z.B. float verwenden. Dazu gibt es diverse MasterPage-Vorlagen.

oder 3. Entfernen der DOCTYPE-Angabe in der MasterPage

Das ist eine schnelle Lösung, aber kein optimales HTML. Eine Triple-A Conformance für “Premium”-Barrierefreiheit wird man dadurch nicht erreichen können. Andererseits: Die default.master wird ja auch so ausgeliefert… ;-)

Englischsprachige Quellen zu diesem Thema:

Technorati Tags: , , ,

12Mrz

Einrichten von Webtests und Loadtest für SharePoint Anwendungen in Visual Studio Team System 2008.

Verbindung zur Datenbank als Load Test Repository.

Hier verlässt man zunächst die VS Oberfläche und begibt sich in die Konsole.  Ich nehme mal  an, es ist ein SQL Server (in meinem Fall SQL Express auf der gleichem Maschine) vorhanden.

Dann einfach VS Command Prompt (über das Startmenü) öffnen und ein CD in folgendes Verzeichnis machen:
c:\program files\microsoft visual studio 9.0\common7\ide

Anschließend folgendes Kommando ausführen (evtl. SQL Instanz anpassen):

SQLCMD /S localhost\sqlexpress /i loadtestresultsrepository.sql

Siehe dazu im Übrigen auch :
http://msdn2.microsoft.com/en-us/library/ms182600.aspx

Artikel vollständig lesen »


23Feb

Eine gute Übersicht, wie am Besten verteilte Standorte mit SharePoint versorgt werden, hat das SharePoint Team in seinem Blog veröffentlicht.

http://blogs.msdn.com/sharepoint/archive/2008/02/22/deploying-microsoft-office-sharepoint-server-2007-geographically.aspx

Die meiner Meinung nach beiden wichtigsten Szenarien habe ich mal rausgegriffen.

1. Aufbau einer zentralen Farm.

Jeder Standort greift über das Netz auf die Inhalte und Dienste der zentralen Farm zu. Auch Suchanfragen laufen über das Netz.

Vorteile Nachteile
kein Crawling von Inhalten über das Netz (WAN) notwendig u.U. langsamerer Zugriff und Datenübertragung, Abhängig von der Bandbreite
zentrale Administration einer Farm u.U. langsamere Suchanfragen, Abhängig von der Bandbreite
zentrales Backup  

 

2. Aufbau einer zentralen Farm mit zusätzlichen regionalen Farmen.

Zu der zentralen Farm kommen an jedem Standort eine regionale Farm hinzu. Und genau hier gibt es jetzt mehrere Möglichkeiten. Hier muss man sich überlegen, ob das SharePoint Poral in erster Linie dazu dient, Inhalte (CMS, Dokumente) bereitzustellen oder für Collaboration genutzt werden soll. http://technet2.microsoft.com/Office/en-us/library/cbccbc4d-e560-4b92-89d0-56f49e7f3f4b1033.mspx?mfr=true

Bei der Suche kann die zentrale Farm die komplette Indizierung aller Farmen über das Netz übernehmen. Dies ist aber möglicherweise sehr langsam und endet in Timeouts. Bei einer schnellen Anbindung ist so aber natürlich ein zentraler Index aller Farmen vorhanden. Benutzer müssen so aber immer zur zentralen Farm navigieren, um eine Suche zu starten.

Bei den Suchergebnissen (Plan for global enterprise search) wird dann aber auch nicht zwischen lokalem Inhalt (der regionalen Farm) und der zentralen Farm unterschieden. Ein Mitarbeiter erkennt nicht gleich, ob das gefundene Dokument auf dem Server seiner Niederlassung oder der zentrale liegt. Es wird dabei auch nicht priorisiert, also dass Suchergebnisse aus der Niederlassung zuerst angezeigt werden.

Die Alternative, dass jede Farm nur ihren eigenen Index hat, halte ich dagegen nicht für sinnvoll, da so nicht in einem Schritt über das ganze Unternehmen gesucht werden kann, was ja dem Gedanken des Enterprise Search widerspricht.

Aber es gibt ja immer eine Möglichkeit:

Die zentrale Farm erstellt über den SSP einen Index über alle Farmen im Verbund. Zusätzlich hat jede regionale Farm einen eigenen Index. Über Suchbereiche kann der Benutzer auswählen, wo er suchen will. Dies reduziert die Suchanfragen über den gesamten Index der zentralen Farm, die Performance der Suchanfragen steigt.

Vorteile Nachteile
lokale Suche möglich, schneller nach wie vor muss der Gesamtindex von der zentralen Farm über das WAN erstellt werden
lokale Relevanz wird berücksichtigt Globale Suche nach wie vor von der Bandbreite abhängig
weniger Traffic über das WAN es müssen mehrere Indizes verwaltet werden

23Jan

Konvertierung von Freemind-Mindmaps in Mindmanager-Mindmaps und umgekehrt

Neben der bekannten, kommerziellen Lösung Mindmanager (von Mindjet) zum Bearbeiten von Mindmaps hat sich mittlerweile ein freies Tool etablieren können. Freemind ist eine Open Source Lösung, lizensiert unter GPL, die viele Funktionen des Mindmanagers abbildet. Die Bedienung von Freemind ist teilweise nicht ganz so komfortabel wie die des Mindmanagers, auch werden Mindmaps nicht automatisch angeordnet, dafür speichert Freemind in weiterverwendbare XML-Dateien und es entstehen keine weiteren Lizenzkosten.

Um Freemind-Mindmaps in Mindmanager-Mindmaps (und umgekehrt) zu konvertieren existieren mehrere Ansätze:

  • Installatation eines Plug-Ins (vorher kostenlose Registrierung notwendig) im Mindmanager, mit dessen Hilfe Mindmaps, die mit Freemind erstellt wurden, geöffnet werden können. Zur Konvertierung in die andere Richtung bietet Freemind die Importfunktion von Mindmanager-Dateien. Achtung: diese Funktion ist erst ab Freemind-Version 0.9 verfügbar.
  • Konvertierung durch XSLTs. Diese Konvertierung ist recht fehleranfällig und umständlich. Durch die neuen Funktionen und Plugins wie oben beschrieben wird dieser Ansatz zur Konvertierung obsolet.
  • Konvertierung ohne zusätzlichen Installationsaufwand, aber etwas „schmutzig“ (Diese Lösung bietet bietet sich vor allem für die schnelle, einmalige Konvertierung an):
    • Export der Mindmap (Alle Zweige müssen beim Export vollständig ausgeklappt sein) mit Freemind in eine HTML-Seite
    • Öffnen und Abspeichern der HTML-Seite in Microsoft Word
    • Import in Mindmanager.

Weil es thematisch gerade passt füge ich an dieser Stelle noch zwei interessante Links zu Mindmapping Tools ein: Artikel vollständig lesen »


08Jan

Dafür gibt es (theoretisch) eine denkbar einfache Lösung: SPListItem bietet eine Methode CopyTo(destinationUrl) an (http://msdn2.microsoft.com/en-us/library/microsoft.sharepoint.splistitem.copyto.aspx) - leider scheint diese aber nicht (in jedem Fall?) zu funktionieren. Zumindest in meinem Fall (benutzerdefinierte Liste mit benutzerdefiniertem Inhaltstyp UND Attachments - vielleicht erwarte ich einfach auch zu viel von Sharepoint…) tat sie es nicht. Stattdessen erhielt ich folgende Exception: “Source item cannot be found. Verify that the item exist and that you have permission to read it.” Eine rasche Recherche bei Google brachte mir die Erkenntnis, dass andere Leute das gleiche Problem auch schon hatten - leider ohne verwertbare Lösungsvorschläge…

Also erstellen wir uns eben selbst eine kleine statische Methode, die das gewünschte tut:

Die Methodensignatur erwartet ein Quellelement und einen Listenname und gibt das kopierte Zielelement zurück:

image

Zuerst erstellen wir das Zielelement in der angegebenen Liste. Dann gehen wir Schritt für Schritt alle Fields des Quellelementes durch und kopieren diese zum Zielelement:

image

Achtung! Wir sollten nicht versuchen, readonly Fields zu kopieren und auch die Attachments lassen sich nicht auf diese Weise “abfertigen”. Diese behandeln wir folgendermaßen:

image

Nun noch schnell das Zielelement gespeichert und zurückgegeben - fertig :-)

image

So könnte z.B. der Aufruf der Methode aussehen:

image

Zum besseren Kopieren hier das Ganze nochmal als Text:

public static SPListItem CopyItem(SPListItem sourceItem, string destinationListName)
{
//copy sourceItem to destinationList
SPList destinationList = sourceItem.Web.Lists[destinationListName];
SPListItem targetItem = destinationList.Items.Add();
foreach (SPField f in sourceItem.Fields)
{
if (!f.ReadOnlyField && f.InternalName != “Attachments”)
{
targetItem[f.InternalName] = sourceItem[f.InternalName];
}
}
//copy attachments
foreach (string fileName in sourceItem.Attachments)
{
SPFile file = sourceItem.ParentList.ParentWeb.GetFile(sourceItem.Attachments.UrlPrefix + fileName);
byte[] imageData = file.OpenBinary();
targetItem.Attachments.Add(fileName, imageData);
}
targetItem.Update();
return targetItem;
}


07Jan

Wenn man in Sharepoint einen benutzerdefinierten Inhaltstyp erstellt und diesen einer Liste zuweist, muss man sich im Idealfall nicht um die Formulare für das Anlegen, Anzeigen und Editieren der Listeneinträge kümmern - dies erledigt Sharepoint anhand der zum Inhaltstyp zugehörigen Spalten freundlicherweise automatisch für uns.

Nun kann es aber passieren, dass wir mit dem von Sharepoint generierten Standardformular nicht zufrieden sind (sei es, weil uns das Layout nicht gefällt oder weil wir z.B. zusätzliche Validierungen und/oder Buttons einbauen wollen…). Auch für diesen Fall wurde vorgesorgt - wir können ein eigenes aspx-Formular erstellen (ja, ich weiß, man könnte auch Infopath nehmen…) und für das Anlegen, Anzeigen oder Editieren der Listeneinträge verwenden. Allerdings habe ich in der Benutzeroberfläche in Sharepoint keine Stelle gefunden, an der man dem Inhaltstyp mitteilen könnte, welche Formulare er jetzt verwenden soll. Hier kann man aber glücklicherweise mit ein paar Zeilen Quellcode (z.B. in einer Konsolenanwendung) Abhilfe schaffen:

image

Es empfiehlt sich, die Formulare unterhalb des Sharepoint LAYOUTS Verzeichnisses (standardmäßig C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS) abzulegen, dies ist aber nicht erforderlich.

Natürlich kann auch z.B. nur für das Anzeigeformular eine eigene aspx-Datei verwendet werden - dann bleiben die restlichen Properties einfach leer (dies ist der Standard) bzw. werden gar nicht erst gesetzt.

Damit das Ganze nicht zu einfach wird, gibt es zum Schluss noch einen kleinen Pferdefuß: Wenn der Inhaltstyp schon einer Liste zugeordnet ist, muss er von der Liste entfernt und wieder zugeordnet werden, damit die Einstellung in dieser Liste wirksam wird!

Technorati Tags: , , , ,