in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

This Blog

Syndication

News

Ein guter Blog lebt auch vom Feedback der Leser. Aus diesem Grund möchte ich alle Leser bitten und auffordern, Feedback und Bewertung für einzelne Posts abzugeben. Ich freue mich natürlich auch, wenn mein Blog oder auch einzelne Posts verlinkt werden. Dies hilft anderen Leser und ist zugleich auch Ansporn für mich!

Meine SharePoint-Notizen

SharePoint-Notizen aus meiner täglichen Projektarbeit mit dem Microsoft Office SharePoint Server 2007
  • How to: Flash-Animationen in SharePoint anzeigen

    Um Flash-Animationen in SharePoint zu verwenden, gibt es unterschiedliche Wege. Der einfachste, aber auch unflexibelste Weg ist, einfach ein Inhaltseditor-WebPart mit ein paar Zeilen HTML-Code zu füllen. Auf diese Weise bekommt man zwar recht schnell eine Flash-Animation auf eine SharePoint-Seite, aber diese Art der Lösung ist mir einfach zu unflexibel. Ich bevorzuge hier eine flexiblere Lösung und setze deswegen ein WebPart ein.

    Für unseren eigenen Internet-Auftritt haben ich ein kleines WebPart entwickelt. Dieses WebPart zieht sich die anzuzeigenen Flash-Animationen aus einer benutzetrdefinierten SharePoint-Liste (bzw. aus dem Anhang eines Eintrags) und zeigt diese Animation dann an. Vorteil dieser Lösung: sowohl die SharePoint-Seite, als auch das WebPart müssen nicht verändert werden, wenn ein andere Flash-Animation angezeigt werden soll.

    Den Rohbau dieses WebParts möchte ich hier veröffentlichen. Um etwas Rechenleistung einzusparen, habe ich im vorliegenden Fall auf den WMODE-Parameter bzw. die Transparenz-Einstellung verzichtet. Solange man 'nur' eine Flash-Animation auf einer SharePoint-Seite anzeigen möchte, sind diese Einstellungen nicht notwendig. Die Member-Variablen m_iFlashWidth und m_iFlashHeight geben die Breite und die Höhe des Ausgabebereichs an. Die Member-Variable m_strFlashUrl enthält die URL der anzuzeigen Flash-Animation. Wer lieber ein Inhaltseditor-WebPart benutzen möchte, um eine Flash-Animation anzuzeigen, der braucht nur den HTML-Code aus der Render-Methode einzufügen und statt der Member-Variablen feste Werte einzusetzen.

    using System.Text;
    using System.Web.UI;
    using System.Web.UI.WebControls.WebParts;

    namespace Technidata.ITS.MOSS.Webparts
    {
        public class ITSFlashPlayer : WebPart
        {
            private int m_iFlashWidth = 300;
            private int m_iFlashHeight = 200;
            private string m_strFlashUrl = "";

            public ITSFlashPlayer()
            {
                ExportMode = WebPartExportMode.All;
            }

            protected override void Render(HtmlTextWriter writer)
            {
                StringBuilder strHTML = new StringBuilder();

                strHTML.Append("<OBJECT classid=clsid:D27CDB6E-AE6D-11cf-96B8-444553540000 ");
                strHTML.Append("codebase=\"
    http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0\" ");
                strHTML.Append(string.Format("Width=\"{0}\" Height=\"{1}\"> ", m_iFlashWidth, m_iFlashHeight));
                strHTML.Append(string.Format("<PARAM NAME=\"movie\" VALUE=\"{0}\"> ", m_strFlashUrl));
                strHTML.Append("<PARAM NAME=\"quality\" VALUE=\"high\"> ");
                strHTML.Append(string.Format("<EMBED src=\"{0}\" quality=high WIDTH=\"{1}\" HEIGHT=\"{2}\" TYPE=\"application/x-shockwave-flash\" PLUGINSPAGE=\"
    http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash\"></EMBED></OBJECT>", m_strFlashUrl, m_iFlashHeight, m_iFlashWidth));

                writer.Write(strHTML.ToString());
            }
        }
    }

    Add to Technorati Favorites

  • Patrick Tisseghem unerwartet verstorben

    Vor wenigen Minuten habe ich hier gelesen, dass Patrick Tisseghem am 3. September in Gothenburg (Schweden) offensichtlich an Herzversagen verstorben ist.

    Ich hatte die Gelegenheit, Patrick auf den letzten SharePoint Conferences persönlich zu treffen und mit ihm zu sprechen. Ich habe Patrick als sehr erfahrenen SharePoint-Architekten kennengelernt, der immer ein offenes Ohr und einen guten Rat für andere SharePoint-Entwickler oder -Architekten hatte. Die wenn auch kurzen Gespräche mit ihm waren mir sehr wertvoll und wichtig.

    Mit Patrick Tisseghem verliert die weltweite SharePoint-Gemeinde ein wertvolles und aktives Mitglied.

    Auch ich möchte seiner seiner Familie mein tiefempfundenes Beileid aussprechen.

     

  • Übersicht aller SharePoint-Versionen

    Im Blog von Aaron Saikovski habe ich eine interessante Aufstellung über die aktuellen SharePoint-Versionen gefunden. 

    Service Pack/Hotfix Version WSS V3.0 MOSS 2007
    Infrastructure Update (KB951695 & KB951297) 12.0.0.6318 12.0.0.6318
    Post-SP1 hotfix (KB953137 & KB953138) 12.0.0.6316.500 12.0.0.6316.500
    Post-SP1 hotfix (KB952698 & KB952704) 12.0.0.6315 12.0.0.6315
    Post-SP1 hotfix (KB948945) 12.0.0.6303 12.0.0.6303
    Post-SP1 hotfix (KB941274) 12.0.0.6301 12.0.0.6301
    Post-SP1 hotfix (KB941422) 12.0.0.6300 12.0.0.6300
    Service Pack 1 12.0.0.6219 12.0.0.6219
    Release To Manufacturing (RTM) 12.0.0.4518 12.0.0.4518

     

    Hier der Link auf den Artikel im Blog von Aaron Saikovski.

     

    Add to Technorati Favorites

  • How to: Professionelle Datenanzeige mit dem Control SPGridView

    Angeregt durch ein WebPart von René Hézser habe ich mir mal das Control SPGridView genauer angesehen. Im Gegensatz zum 'normalen' GridView wird SPGridView von SharePoint selbst verwendet, um z.B. den Inhalt von SharePoint-Listen anzuzeigen. Der große Vorteil des SPGridView: dieses Control bietet Unterstützung für Sortierung, Filterung und Paginierung - und passt sich ohne weitere Programmierung an das Layout von SharePoint an.

    Im Internet finden sich einige interessante Artikel darüber, wie man eine Datenanzeige mit einem SPGridView realisieren kann. Aber jeder dieser Artikel behandelt leider immer nur einen Teilaspekt - ein umfassendes Beispiel zum Thema SPGridView habe ich nicht gefunden. Aus diesem Grund habe ich selbst das folgende Beispiel geschrieben. Es zeigt beispielhaft die Datenanzeige mit einem SPGridView mit Sortierung, Paginierung und Filterung. Leider zeigt das SPGridView beim Filtern von Spalten kein Filter-Icon an, wie man es von SharePoint-Listen kennt. Hier musste ich selbst ein wenig recherchieren - aber mit Hilfe von Greg Galipeau konnte ich auch dafür eine Lösung finden. Der Trick besteht darin, sich an das Event RowDataBound zu hängen, hier die gefilterte Spalte auszulesen und mit diesen Informationen das Filter-Icon selbst in der Kopfzeile des SPGridView in der entsprechenden Spalte anzuzeigen.

    Die folgenden beiden Screenshots zeigen das SPGridView in Aktion. Im zweiten Screenshot sieht man, dass das SPGridView auch die Sortier- und Filtericons verwenden und anzeigen kann.

    Bevor wir uns jetzt auch noch den Sourcecode dazu ansehen, möchte ich noch darauf hinweisen, dass der folgende Code ausschließlich als Beispiel bzw. Anschauungsobjekt gedacht ist. Er ist in der vorliegenden Form nicht dafür geeignet, unverändert in eine produktive Umgebung übernommen zu werden.

    using System.Data;
    using System.Reflection;
    using System.Web.UI.WebControls;
    using Microsoft.SharePoint.WebControls;
    using System.Web;

    namespace Technidata.ITS.MOSS.Webparts
    {
        public class SPGridViewDemo : System.Web.UI.WebControls.WebParts.WebPart
        {
            private SPGridView m_oGridView;
            private ObjectDataSource oDataSource;

            public SPGridViewDemo()
            {
            }

            protected override void CreateChildControls()
            {
                m_oGridView = new SPGridView();

                // Set basic gridview properties
                m_oGridView.ID = "MyGridView";
                m_oGridView.AutoGenerateColumns = false;
                m_oGridView.AlternatingRowStyle.BackColor = System.Drawing.Color.WhiteSmoke;

                // Set properties for sorting
                m_oGridView.AllowSorting = true;

                // Set properties for paging
                m_oGridView.PageSize = 3;
                m_oGridView.AllowPaging = true;
                m_oGridView.PagerStyle.HorizontalAlign = HorizontalAlign.Center;

                // Set properties for filtering
                m_oGridView.AllowFiltering = true;
                m_oGridView.FilterDataFields = "Vorname,Nachname";
                m_oGridView.FilteredDataSourcePropertyName = "FilterExpression";
                m_oGridView.FilteredDataSourcePropertyFormat = "{1} LIKE '{0}'";

                // Set EventHandler for setting the filter icon
                m_oGridView.RowDataBound += new GridViewRowEventHandler(m_oGridView_RowDataBound);
               
                // Set columns of the SPGridView
                CreateGridViewColumns("Vorname");
                CreateGridViewColumns("Nachname");

                // Create the datasource object
                oDataSource = new ObjectDataSource();

                string strTypeName = "Technidata.ITS.MOSS.Webparts.SPGridViewDemo,";
                strTypeName += Assembly.GetExecutingAssembly().FullName;

                oDataSource.TypeName = strTypeName;
                oDataSource.SelectMethod = "FillDataTable";
                oDataSource.ID = "MyDataSource";

                HttpRequest oRequest = HttpContext.Current.Request;
                if ((oRequest.Form["__CALLBACKID"] == null)    ||
                    (oRequest.Form["__CALLBACKPARAM"] == null) ||
                    (!oRequest.Form["__CALLBACKID"].EndsWith("MyGridView")))
                {
                    if (null != ViewState["FilterExpression"])
                    {
                        oDataSource.FilterExpression = (string)ViewState"FilterExpression"];
                    }
                }

                Controls.Add(oDataSource);

                // Bind SPGridView to datasource object
                m_oGridView.DataSourceID = oDataSource.ID;

                Controls.Add(m_oGridView);

                // IMPORTANT! Call this line after Controls.Add()
                m_oGridView.PagerTemplate = null
            }

            // --------------------

            protected override void OnPreRender(System.EventArgs e)
            {
                ViewState["FilterExpression"] = oDataSource.FilterExpression;
                base.OnPreRender(e);
            }

            // --------------------

            protected override void Render(System.Web.UI.HtmlTextWriter writer)
            {
                m_oGridView.DataBind();
                base.Render(writer);
            }

            // --------------------

            void m_oGridView_RowDataBound(object sender, GridViewRowEventArgs e)
            {
                if ((null != sender) && (e.Row.RowType == DataControlRowType.Header))
                {
                    string strFilteredColumn = ((SPGridView)sender).FilterFieldName;
                    SetGridViewFilterIcon(m_oGridView, strFilteredColumn, e.Row);
                }
            }

            // --------------------

            public void SetGridViewFilterIcon(SPGridView oGridView, string strColumn, GridViewRow oRow)
            {
                if ((false == string.IsNullOrEmpty(strColumn)) && (null != oRow))
                {
                    // Show icon on filtered column
                    for (int iIndex = 0; iIndex < oGridView.Columns.Count; iIndex++)
                    {
                        DataControlField oField = oGridView.Columns[iIndex];

                        if (oField.HeaderText == strColumn)
                        {
                            Image oFilterIcon = new Image();
                            oFilterIcon.ImageUrl = "/_layouts/images/ewr093.gif";
                            oFilterIcon.ImageAlign = ImageAlign.Left;
                            oFilterIcon.Style[System.Web.UI.HtmlTextWriterStyle.MarginTop] = "2px";
                            oFilterIcon.ID = "FilterIcon";

                            Panel oPanel = new Panel();
                            oPanel.Controls.Add(oFilterIcon);

                            oRow.Cells[iIndex].Controls.Add(oPanel);

                            break;
                        }
                    }
                }
            }

            // --------------------

            public static DataTable FillDataTable()
            {
                DataTable oDataTable = new DataTable("Bond Actors");
                DataRow row = null;

                // Create the columns
                oDataTable.Columns.Add("Vorname", typeof(string));
                oDataTable.Columns.Add("Nachname", typeof(string));

                // Fill data table
                row = oDataTable.NewRow();
                row["Vorname"] = "Sean";
                row["Nachname"] = "Connery";
                oDataTable.Rows.Add(row);

                row = oDataTable.NewRow();
                row["Vorname"] = "George";
                row["Nachname"] = "Lazenby";
                oDataTable.Rows.Add(row);

                row = oDataTable.NewRow();
                row["Vorname"] = "Roger";
                row["Nachname"] = "Moore";
                oDataTable.Rows.Add(row);

                row = oDataTable.NewRow();
                row["Vorname"] = "Timothy";
                row["Nachname"] = "Dalton";
                oDataTable.Rows.Add(row);

                row = oDataTable.NewRow();
                row["Vorname"] = "Pierce";
                row["Nachname"] = "Brosnan";
                oDataTable.Rows.Add(row);

                row = oDataTable.NewRow();
                row["Vorname"] = "Daniel";
                row["Nachname"] = "Craig";
                oDataTable.Rows.Add(row);

                return oDataTable;
            }

            // --------------------

            private void CreateGridViewColumns(string strColumn)
            {
                BoundField oGridColumn = new BoundField();
                oGridColumn.DataField = strColumn;
                oGridColumn.HeaderText = strColumn;

                // IMPORTANT! This line is needed to display the arrow icons
                oGridColumn.SortExpression = strColumn;
                oGridColumn.Visible = true;

                m_oGridView.Columns.Add(oGridColumn);
            }
        }
    }

     

    Nachtrag: ich bin noch auf ein kleines Problem gestossen: wenn gleichzeitig die Sortierung und die Filterung aktiviert ist und man dann die Sortierreihenfolge ändert, funktioniert das Filtern nicht mehr. Aber auch dafür findet sich eine Lösung - in diesem Fall habe ich mir ein paar Zeilen Code bei Bob's SharePoint Bonanza ausgeliehen und gleich in mein obiges Beispiel integriert. Das Prinzip ist: man sichert in den ViewStates den aktuellen Filter und setzt den Filter aus den ViewStates beim Erzeugen bzw. Parametrieren des Objekts wieder.


     

    Add to Technorati Favorites

  • SharePoint-Listen und die Namen der Spalten

    SharePoint nutzt u.a. zur Speicherung von Daten Listen und Dokumentbibliotheken. Neben einigen vordefinierten Listen und Bibliotheken kann man auch benutzerdefinierte Listen erstellen und an die eigenen Bedürfnisse anpassen. Bei diesen benutzerdefinierten Listen kann man selbst festlegen, welche Spalten verwendet werden sollen und welchen Datentyp diese Spalten aufnehmen sollen. Und genau bei diesen Spalten bzw. den Namen der Spalten gibt es einiges zu beachten.

    Wenn man eine neue Spalte in einer benutzerdefinierten Liste anlegen möchte, wechselt man in die Listen-Einstellungen und klickt dort auf 'Spalte erstellen'. Jetzt gibt man der neuen Spalte einen Namen und wählt einen Datentyp aus einer Liste vordefinierter Datentypen aus. Das sieht dann z.B. so aus, wie in diesem Screenshot dargestellt:

     

    Auch wenn man der Spalte nur einen einzigen Spaltennamen zuweisen kann (was auf den ersten Blick auch logisch und völlig selbstverständlich erscheint), existieren neben dem Spaltennamen, den man beim Erstellen einer neuen Spalte vergibt noch zwei weitere Spaltennamen. Diese beiden zusätzlichen Spaltennamen bekommt ein SharePoint-User nur sehr selten zu sehen, ein Programmierer, der ein WebPart, einen Eventhandler, einen Workflow oder einen Timerjob programmiert, sollte aber schon sehr genau wissen, was es mit diesen zusätzlichen Spaltennamen auf sich hat.

    Der Spaltenname, der beim Erstellen einer neuen Spalte angegeben wird, wird als Display Name bezeichnet. Wie dieser Name schon vermuten läßt, ist der Display Name der Anzeigename einer Spalte. Dies bedeutet: überall in der Oberfläche wo es um das Anzeigen von Daten und Informationen geht, wird der Display Name (Anzeigename) verwendet.

    Beim Erstellen einer neuen Spalte erzeugt SharePoint (oder besser gesagt das unterlagerte Objekt-Modell) zwei weitere Spaltennamen: den Internal Name und den Static Name. Von diesen beiden Spaltennamen bekommt ein SharePoint-User kaum etwas zu sehen und auch ein SharePoint-Administrator oder ein Designer werden diese beiden zusätzlichen Spaltennamen nur selten zu Gesicht bekommen. Dennoch sind beide sehr wichtig und sollten besonders von Entwicklern beachtet werden.

    • der Display-Name wird auch als Anzeigename bezeichnet und wird vom SharePoint-Benutzer beim Anlegen einer Spalte angegeben.
    • der Static-Name wird von SharePoint beim Erzeugen einer neuen Spalte automatisch erzeugt.
    • der Internal-Name wird ebenfalls von SharePoint beim Erzeugen einer neuen Spalte erzeugt.

    Jetzt wissen wir, dass es nicht nur einen Spalten-Namen gibt, sondern drei - aber wozu braucht man denn drei Spaltennamen?

    In erster Linie sind diese drei Spalten-Namen für Programmierer interessant. Da der Display-Name (also der Anzeigename) nachträglich verändert werden kann, besteht gerade für die Programmierung die Notwendigkeit, nicht-veränderliche Spaltennamen zu haben, um Spalten auch nach der Änderung des Display-Name noch identifizieren zu können. Beim Verändern des Display-Names werden weder Internal-Name noch Static-Name verändert, dennoch ist der Static-Name nicht ganz so unveränderlich, wie der Internal-Name. Im Gegensatz zum Internal-Name kann der Static-Name nachträglich über Aufrufe aus dem SharePoint Objekt-Modell verändert werden. Static-Name und Internal-Name müssen nicht zwingend gleich lauten.

    • der Display-Name ist vom Benutzer direkt über die SharePoint-Oberfläche veränderbar und kann auch nachträglich beliebig oft umbenannt werden.
    • der Static-Name bleibt beim Umbenennen einer Spalte unverändert, ist aber nicht wirklich unveränderbar. Über das Objekt-Modell kann der Static-Name verändert werden.
    • der Internal-Name bleibt ebenfalls beim Umbenennen unverändert und kann auch über Objekt-Modell Aufrufe nicht mehr verändert werden.

    Um den Static-Name zu ändern, reichen im Prinzip die folgenden Code-Zeilen: 

    SPList oList = ...
    string strColumnName = ...

    oList.Fields.GetFieldByInternalName(strColumnName).StaticName = "NeuerName";
    oList.Fields.GetFieldByInternalName(strColumnName).Update(true);


    Der Internal-Name scheint der wirklich eindeutige und unveränderbare Namen einer Spalte zu sein. Der Static-Name ist für die Programmierung interessant - ein Beispiel dafür ist die Webpart-Programmierung. Bei uns haben wir Richtlinien (oder neu-deutsch: Best Practices) erarbeitet, wie wir welchen Spaltennamen bei der Programmierung einsetzen. Wenn bei einem Webpart über die Webpart-Properties z.B. ein Spaltenname einer SharePoint-Liste angegeben werden soll, verwenden wir dafür grundsätzlich den Display-Name, um dem Designer die Arbeit der WebPart-Parametrierung zu erleichtern. Der Designer kann bei der Parametrierung eines WebParts die gleichen Spaltennamen eingeben, die er auch beim Anlegen der Liste verwendet hat.

    Allerdings verwendet dann das eigentliche WebPart natürlich nicht mehr den in die Properties eingegebenen Display-Name, sondern wandelt diesen mit Hilfe der zugehörigen Liste in den Internal-Name. Intern arbeiten unsere WebParts grundsätzlich mit dem Internal-Name. Es hat für uns aber manchmal einen Vorteil, statt des unveränderlichen Internal-Name den nur bedingt unveränderlichen Static-Name zu verwenden. Dieser Vorteil wird deutlich, wenn wir das Thema Mehrsprachigkeit bzw. Variations betrachten. Angenommen wir haben zwei gleiche Listen - eine Liste mit deutschen Anzeigenamen und eine zweite Liste mit englischen Anzeigenamen. Mit beide Listen soll ein selbstprogrammierter Eventhandler verbunden werden (ein Beispiel dazu findet sich hier), der beim Beschreiben einer bestimmten Spalte getriggert werden soll. In diesem Fall würde der Eventhandler mit dem Static-Name der Triggerspalte arbeiten, denn der Display-Name wird sich aufgrund der unterschiedlichen Sprachen unterscheiden. Da der Static-Name zum Glück nicht vollständig unveränderlich ist, kann man -trotz unterschiedlichem Display-Name- den Static-Name nachträglich angleichen. Auf diese Weise kommt man mit einem Eventhandler für zwei sprachlich unterschiedliche Listen aus.

    Wie ändert man denn nun den Static-Name einer SharePoint-Spalte, wenn dies nicht über die Oberfläche, sondern nur über das Objekt-Modell geht? Um dies zu zeigen, habe ich ein kleines Tool geschrieben, welches auf unserer Homepage kostenlos zum Download (gegen eine kleine Registrierung) bereitsteht. Dieses Tool erlaubt die Auswahl eines Webs und einer Liste und zeigt die in der Liste vorhandenen Spalten mit ihrem Internal-Name an. Durch Anklicken einer Spalte kann man diese auswählen und dann ggf. Display-Name und Static-Name ändern. Man kann sich so einen schnellen Überblick über alle Spalten einer Liste verschaffen. Bei diesem Tool handelt es sich um ein Entwickler-Tool, welches wir intern bei unseren Entwicklungen nutzen. Für diesen Beitrag habe ich es ein wenig angepasst und aufgeräumt - dennoch bleibt es ein Entwickler-Tool und der Einsatz erfolgt grundsätzlich auf eigenes Risiko!

    Bei der Programmierung bzw. bei der Verwendung des SharePoint Objekt-Modells muss man aber sehr aufpassen, welchen Spaltennamen man mit welcher Methode verwenden kann bzw. soll. Leider ist dies im SDK nicht einheitlich geregelt. Es gibt Methoden, bei denen schon aufgrund des Methodennamens klar ist, mit welchem Spaltennamen sie arbeiten: SPFieldCollection.GetFieldByInternalName(name) arbeitet wie der Name bereits andeutet, mit dem Internal-Name einer Spalte, während SPFieldCollection[name] den Display-Name verwendet. Bei SPListItem[name] wird die Sache noch etwas undurchsichtiger: hier sind alle drei Spaltennamen als Parameter erlaubt. Eine erste Übersicht von Methoden und deren Spalten-Parametern findet sich in diesem Blog.

    Um zu zeigen, dass es nicht schwierig ist, bei der Programmierung die einzelnen Spaltennamen sauber zu trennen, hier ein kleines Snippet, wie man mit dem Display-Name den zugehörigen Internal-Name ermitteln kann.

    private string GetInternalColumnName(SPList oList, string strColumnDisplayName)
    {
        string strRetParam = string.Empty;

        try
        {
            strRetParam = oList.Fields.GetField(strColumnDisplayName).InternalName;
        }
        catch
        {
            strRetParam = strColumnDisplayName;
        }

        return (strRetParam);
    }
     

    Ich hoffe, dass ich mit diesem Beitrag zeigen konnte, wie wichtig es ist, gerade bei der Programmierung darauf zu achten, die unterschiedlichen Spaltennamen-Typen sauber zu trennen und sich vor Beginn der Programmierung zu überlegen, mit welchem Typ von Spaltennamen gearbeitet werden soll. Aus eigener Erfahrung kann ich sagen, dass es sehr aufwändig werden kann, Sourcecode zu debuggen, bei dem dauernd und scheinbar wahllos zwischen allen Spaltennamen-Typen gewechselt wird. Auch hier gilt: ein klar strukturiertes und durchdachtes Design macht das Programmieren und das Debuggen wesentlich einfacher!

    Bisher habe ich erstaunlich wenige Blog-Posts zu diesem Thema gefunden - Grund genug für mich, meine eigenen Erfahrungen zu diesem Thema in meinem Blog zusammenzufassen.

    Nachtrag: der Link zum kostenlosen Download unserer SharePoint-Tool geht im Text dieses Beitrags etwas unter. Deswegen hier nochmals der Link zu unserer SharePoint-Seite.

     

    Add to Technorati Favorites
  • How To: Wie behebt man das Problem mit der Add-On Meldung bzgl. der NAME.DLL?

    Vielleicht hat der eine oder andere Leser meines Blogs dieses Problem selbst schon einmal gehabt: man surft im Internet und möchte sich eine neue auf SharePoint-basierende Site ansehen und bekommt von seinem Browser folgende Meldung angezeigt:

     

    Gerade bei Internet-Auftritten von Unternehmen ist diese Meldung nicht nur lästig, sondern wirkt auf Kunden oft auch befremdlich. In Zeiten eines zum Glück deutlich gestiegen Sicherheitsbewußtseins beim Surfen im Internet hinterläßt eine solche Meldung bei vielen Surfern ein ungutes Gefühl.

    Bei Intranet-Auftritten ist die NAME.DLL, die sich letztendlich hinter dieser Meldung verbirgt, nützlich und hilfreich. Dahinter verbirgt sich u.a. die Smart-Tag bzw. Presence-Funktionalität. Bei Internet-Auftritten hat diese NAME.DLL meist aber keine sinvolle Funktion, sondern stört eher dadurch, dass es Besucher einer Site verwirrt oder sogar abschreckt.

    Microsoft beschreibt in einem Artikel zu KB931509, wie man dieses Problem lösen kann. Hier werden mehrere Lösungsmöglichkeiten beschrieben, denn man kann dieses Problem auch einfach dadurch lösen, dass man die betreffende Site zur Liste der vertrauenswürdigen Sites im IE7 hinzufügt. Für Intranet-Sites mag das eine Lösung sein, bei Internet-Auftritten wird man seinen Besuchern kaum zumuten wollen, den neuen Firmenauftritt als vertrauenswürdig einzustufen! Im KB-Artikel von Microsoft wird aber auch eine serverseitige Lösungsmöglichkeit beschrieben, die ein paar Änderungen an der Masterpage voraussetzt.

    Übrigens: im Zusammenhang mit einem SharePoint Add-On gibt es noch ein weiteres bekanntes Problem, welches sogar den IE7 zum Abstürzen bringen kann! Darüber und über eine Lösungsmöglichkeit habe ich hier bereits vor einiger Zeit berichtet.

     

    Add to Technorati Favorites
  • How to: Mails mit Anhang aus Workflows heraus verschicken

    Das neue VisualStudio 2008 unterstützt einen Entwickler sehr gut bei der Erstellung von Workflows für SharePoint. Besonders der grafische Editor erleichtert das Erstellen der Workflow-Grundstruktur enorm. Momentan arbeite ich an einem benutzerdefinierten Workflow für einen inhouse Kunden. Dieser Workflow wird von einer SharePoint-Liste beim Einfügen eines neuen Elements getriggert. Eine der Aufgaben dieses Workflows soll das Verschicken einer E-Mail sein. Keine große Sache - schließlich bietet der grafische Workflow-Editor in VisualStudio 2008 das SendMail-Objekt genau für solche Zwecke an. Aber sobald man versucht, mit diesem SendMail-Objekt eine E-Mail mit einem Anhang (Attachment) zu verschicken, stößt man auf Schwierigkeiten. Das SendMail-Objekt unterstützt leider keine Attachments!

    Es blieb mir also nichts anderes übrig, als statt des SendMail-Objekts eine normale Code-Activity zu verwenden. Erschwerend kommt noch hinzu, dass die Datei, die als E-Mail Attachment verschickt werden soll, nicht im Dateisystem abgelegt wurde, sondern als Anhang an einem Listen-Eintrag einer SharePoint-Liste vorlag. Es ergaben sich also 2 Probleme, für die ich eine Lösung brauchte:

    1. wie verschickt man E-Mails mit Anhang?
    2. wie bekommt man den Dateianhang aus der Liste heraus und in die E-Mail herein?

    Das erste Problem läßt sich mit der Klasse System.Net.Mail.MailMessage lösen. Dieses Objekt läßt sich ähnlich verwenden, wie das SendMail-Objekt, unterstützt aber Anhänge. Im Gegensatz zum SendMail-Objekt muss man aber hier den zu verwendenden SMTP-Server und die Absenderadresse angeben. Beides ist normalerweise in SharePoint (über die Zentraladministration) bereits definiert und man kann sich diese Einstellungen über das Objektmodell besorgen.

    Das zweite Problem ist etwas kniffliger. Über den Dateinamen und die URL des SharePoint-Listen-Anhangs kann man sich ein SPFile-Objekt erzeugen. Dieses SPFile-Objekt kann man dann durch ein Attachment-Objekt auslesen lassen. Danach kann man dieses Attachment-Objekt der Attachments-Collection von MailMessage hinzufügen.

    Auch wenn es ursprünglich nach einer einfachen Sache aussah, stellte sich das Problem "wie versende ich eine E-Mail mit Dateianhang aus einem Workflow" als interessante Herausforderung dar. Für interessierte Leser füge ich diesem Posting ein kleines Code-Snippet bei. Es zeigt eine Möglichkeit, wie man einen (bzw. den ersten) Dateianhang eines Eintrags in einer SharePoint-Liste als Anhang einer E-Mail verschicken kann.

    private void SendCustomerMail_ExecuteCode(object sender, EventArgs e)
    {
       string strPathAttachment = string.Empty;
       SPListItem oItem = workflowProperties.Item;
       SPWebApplication oWebApp = workflowProperties.Site.WebApplication;

       MailMessage oMail = new MailMessage();

       oMail.From = new MailAddress(oWebApp.OutboundMailSenderAddress);
       oMail.To.Add(m_strMailAddressCustomer);
       oMail.Subject = m_strMailSubjectCustomer;
       oMail.Body = m_strMailTextCustomer;
       oMail.IsBodyHtml = false;

       SPAttachmentCollection oAttachments = oItem.Attachments;

       if (0 < oAttachments.Count)
       {
         strPathAttachment = oAttachments.UrlPrefix + oAttachments[0];

         SPFile oFile = oItem.ParentList.ParentWeb.GetFile(strPathAttachment);
         Attachment oAttachment = new Attachment(oFile.OpenBinaryStream(), oFile.Name);

         oMail.Attachments.Add(oAttachment);
       }

       string strSmtpServer = oWebApp.OutboundMailServiceInstance.Server.Address;
       SmtpClient oSmtpClient = new SmtpClient(strSmtpServer);
       oSmtpClient.UseDefaultCredentials = true;

       oSmtpClient.SendAsync(oMail, "UserState");
    }

    An dieser Stelle möchte ich noch auf ein älteres Posting von mir hinweisen: dem MailMessage-Objekt kann man per Eigenschaft (IsBodyHTML) mitteilen, dass es die E-Mail als 'Plain-Text' verschicken soll. Beim SendMail-Objekt geht das leider nicht so einfach. Es ist aber dennoch möglich - hier der Link zu meinem Beitrag.

     

    Add to Technorati Favorites
  • Programmierung: Potentielle Memory-Leaks bei SPSite und SPWeb

    Beim Programmieren im SharePoint-Umfeld sollte man großen Wert auf saubere und sichere Programmierung legen. Kaum etwas ist ärgerlicher, als wenn das selbstentwickelte WebPart im Portal des Kunden bei jedem Aufruf Speicher anfordert und diesen nicht wieder zurück gibt.

    Gerade beim SharePoint Objektmodell sollte man sehr genau wissen, was man macht bzw. wie man es macht - dies gilt besonders bei der Verwendung von SPSite und SPWeb.

    Eine gute Zusammenfassung über das Thema habe ich im Blog von Roger Lamb gefunden. Sein Artikel zum Thema SharePoint 2007 and WSS 3.0 Dispose Patterns by Example ist für alle SharePoint-Programmierer sehr interessant.

     

    Add to Technorati Favorites
  • Desktop-Icons / Layout speichern und Wiederherstellen

    Wenn ich für einen Vortrag oder eine Präsentation zu einem Kunden fahre, nehme ich gern mein Präsentations-Notebook mit und schließe es vor Ort an einen Beamer an. Mein Notebook paßt dann aber die Auflösung an die maximale Auflösung des angeschlossenen Beamers an und zerwürfelt mir dabei jedesmal die Sortierung der Icons auf meinem Desktop. Da ich meist einige Icons auf meinem Desktop habe, ist ein Punkt der üblichen Nachbereitung das manuelle (und lästige) Wiederherstellen meines gewohnten Desktop-Icon Layouts.

    Ich denke, jeder der schon einmal eine Präsentation mit seinem Notebook und einem Beamer gehalten hat, kennt dieses Problem.

    Bei meiner letzten Kundenpräsentation hatte ein Mitarbeiter im Anschluß an meinen Vortrag einen guten Tipp für mich: es gibt ein kleines Tool, mit dem man das aktuelle Icon-Layout auf dem Desktop speichern und später bei Bedarf wieder herstellen kann. Dieses praktische Tool kann man sich hier herunterladen.

    Leider klappt die Installation nur für Systeme, auf denen Windows XP läuft. Wer Vista oder -wie ich- Vista 64-Bit einsetzt, findet eine erweiterte Version hier.

    So eine Funktion sollte es direkt in Windows Vista geben ...

    Nachtrag: von Midi-Ox gibt es nun auch eine Lösung, um die Icon-Position auf dem Desktop zu speichern und wiederherzustellen.

     

    Add to Technorati Favorites
  • How to: Sharepoint-Liste als eigenen Suchbereich einrichten

    Die Suche von SharePoint ist ein sehr mächtiges Instrument und mit ein wenig Konfigurationsarbeit kann man sehr mächtige Suchcenter einrichten. Allerdings gibt es ein Manko: man kann keine einzelne SharePoint-Liste direkt als eigenen Suchbereich definieren. Als Basis für einen Suchbereich läßt SharePoint nur die folgende Inhaltsquellen zu: SharePoint-Websites, allg. Websites, Dateifreigaben, öffentliche Exchange-Ordner und Geschäftsdaten zu (siehe folgendes Bild).

     

    In unserem eigenen Intranet (natürlich basiered auf einem SharePoint Server 2007) existiert eine Bücherliste, die mittlerweile beachtliche Ausmaße angenommen hat. Bisher wurde diese Bücherliste in den allgemeinen Suchindex aufgenommen. Leider bedeutet dies aber auch, dass Mitarbeiter u.U. eine größere Suchergebnisliste angezeigt bekommen, selbst wenn sie nur nach einem Buch gesucht haben. Aus diesem Grund habe ich mir Gedanken darüber gemacht, wie man unsere Bücherliste als eigenständigen Suchbereich einrichten kann. Mein Ziel ist: ich möchte in unserem Intranet-Suchcenter einen eigenen Suchbereich Bücherliste einrichten und die Ergebnisliste soll nur aus Einträgen aus dieser Bücherliste bestehen.

    In diesem Post möchte ich eine auf Metadaten basierte Lösung vorstellen.

    Zuerst habe ich in unserer Bücherliste eine neue Spalte eingefügt. Diese Spalte bekommt den Namen MetaTagBookList und ist vom Typ Text. Da hier niemand Daten eingeben soll (bzw. darf), wird diese Spalte fest auf den Defaultwert Bücherliste gesetzt. Diese Spalte soll nur dazu dienen, jeden Eintrag in der Bücherliste mit einem festen MetaTag zu kennzeichnen. Um sicherzustellen, dass hier niemand einen anderen Wert eingibt oder verändert, empfiehlt es sich, diese Spalte aus den Ansichten auszublenden. Wer dies gründlich machen möchte, kann dazu mein SharePoint-Tool "List Field Form Properties" aus diesem Post benutzen. Etwas unschön ist nur die Tatsache, dass man nun alle Einträge in dieser Liste nochmals öffnen und speichern muss, damit jeder Eintrag in der Bücherliste einen gesetzten MetaTag Bücherliste hat. (Dieses Problem läßt sich aber zum Glück mit ein paar Zeilen Code und einem kleinen Entwicklertool lösen.)

    So - nun ist die Bücherliste vorbereitet. Als nächstes muss unsere neu-hinzugefügte Spalte MetaTagBookList in die Liste der SharePoint-Metadaten aufgenommen werden. Hierzu startet man einen Crawl der betreffenden Inhaltsquelle. Eigentlich sollte ein inkrementelles Crawling ausreichend sein. Dies hat bei mir aber nicht immer geklappt, weswegen ich dazu übergegangen bin, einen vollständigen Crawl zu starten.

    Nachdem das Crawling erfolgreich abgeschlossen wurde, sollten wir unseren neuen Metadaten-Tag MetaTagBookList in der Liste der Metadaten wiederfinden. Dazu öffnen wir die Zentraladministration, klicken auf den SharedService und danach unter der Rubrik Suchen auf den Link Sucheinstellungen. Es öffnet sich eine neue Seite mit den Crawleinstellungen. Ein paar Zeilen tiefer findet sich der Link Eigenschaftenzuordnung für Metadaten, auf den wir jetzt klicken. Es öffnet sich nun eine Liste mit den Eigenschaftenzuordnungen für Metadaten. Hier klicken wir auf Neue verwaltete Eigenschaft und fügen im folgenden Dialog eine neue verwaltete Eigenschaft mit dem Namen Bücherliste hinzu und wählen aus, dass wir diese Eigenschaft in Suchbereichen verwenden möchten! (siehe folgendes Bild).

    Nun haben wir eine neue verwaltete Eigenschaft mit dem Namen Bücherliste erzeugt - und diese können wir benutzen, um einen entsprechenden Suchbereich zu definieren. Hierzu gehen wir zurück zu den SharedService-Einstellungen in der Zentraladministration und klicken wieder auf den Link Sucheinstellungen im Bereich Suchen. Wir lassen uns alle Bereiche anzeigen und klicken oben auf Neuer Bereich. Es öffnet sich ein Dialog und wir geben dort unserem neuen Bereich den Namen Bücherliste und wählen aus, dass wir die Standardseite für Suchergebnisse verwenden wollen (siehe folgendes Bild).

    Als nächstes müssen wir noch eine Regel für diesen neuen Bereich hinzufügen - schließlich wollen wir nur Ergebnisse aus der Bücherliste als Ergebnis einer Suche in diesem neuen Bereich sehen. Wir klicken also auf Regel hinzufügen und wählen Eigenschaftsabfrage aus. Darunter wählen wir in der Dropdownbox Suchkriterien unsere Bücherliste aus und im darunterliegenden Textfeld geben wir "Bücherliste" ein. Bedeutet: wir wollen nur Ergebnisse, bei denen die Eigenschaft Bücherliste den Wert "Bücherliste" hat. Damit sichergestellt ist, nur Ergebnisse aus der Bücherliste zu bekommen, müssen wir unter Verhalten noch "Erforderlich - Jedes Element muss mit dieser Regel übereinstimmen"  auswählen. (siehe folgendes Bild).

    Nach einer Aktualisierung der Bereiche (kann automatisch erfolgen oder manuell angestoßen werden) sollte unser neuer Bereich existieren. Nun können wir diesen Bereich als weiteren Bereich für die Suche definieren. Dazu öffnen wir die Websiteeinstellungen der obersten Ebene und klicken auf Suchbereich unter der Rubrik Websitesammlungsverwaltung. In der Anzeigegruppe Suchdropdown fügen wir unseren neuen Bereich Bücherliste hinzu (siehe Bild).

    Sollte in der Spalte Einträge 0 angezeigt werden, ist u.U. ein weiterer Crawl-Durchlauf erforderlich. Nun können wir unser Suchcenter aufrufen und sollten im Bereichsdropdown unseren neuen Bereich Bücherliste vorfinden. Zeit für einen ersten Probelauf: Bereich Bücherliste auswählen und einen Suchbegriff eingeben, der in der Bücherliste vorkommt. Im Suchergebnis sollten jetzt nur Ergbnisse aus unserer Bücherliste auftauchen. Für diesen Post habe ich mir eine kleine Demo erstellt (aus der auch die Screenshots stammen) und da sieht das Ergebnis nun so aus, wie im folgenden Bild dargestellt.

    Diese Metadaten kann man auch verwenden, um ein Suchcenter zu erstellen, welches z.B. nur und ausschließlich in unserer Bücherliste sucht. Dies ist dann interessant, wenn man ein Suchcenter mit mehreren spezialisierten Suchfeldern auf unterschiedlichen Seiten haben möchte. Eine kleine Modifikation am Suchfeld-Webpart macht dies möglich. Dazu öffnet man die Webpart-Eigenschaften des Suchfeld-Webparts und trägt in der Rubrik Abfragetextfeld in das Textfeld Zusätzliche Abfrageausdrücke "Bücherliste:Bücherliste" ein - bedeutet auch hier wieder: Bücherliste muss den Wert "Bücherliste" haben. Nun kann man das Bereichsdropdownfeld noch ausblenden und fertig ist ein Suchfeld, dass ausschließlich in unserer Bücherliste sucht (siehe folgendes Bild).


     

    Zusammenfassung: Auf diese Weise kann man Benutzern eines Suchcenters sehr anwenderfreundliche Suchbereiche zur Verfügung stellen und es ermöglichen, Suchergebnisse auf eine SharePoint-Liste zu beschränken. Wer die Ausgabe des Suchergebnisses noch anpassen möchte (z.B. das Buchcover einblenden), der findet bei Tobias Zimmergren einen guten Artikel über dieses Thema.

     

    Add to Technorati Favorites
  • Interessantes über das "! Neu" Tag in SharePoint-Listen

    Ehrlich gesagt - meine Kollegen haben mich das schon einige Male gefragt, aber ich konnte Ihnen keine kompetente Antwort auf die Frage "Wie lange wird eigentlich dieser grüne ! Neu-Schriftzug angezeigt?" geben. Nun habe ich die Antwort darauf in einem Blogpost von Asif Rehmani gefunden. Hier der Link zum Post.

    Zusammengefasst: man kann mit STSADM steuern, ob und wie lange dieses Tag angezeigt wird. Dies geht mit diesem Aufruf:

    stsadm.exe –o setproperty –pn days-to-show-new-icon –pv (number of days) –url (Virtual server address) 

    Um das Tag ganz abzuschalten, kann man diesen Aufruf verwenden:

    stsadm.exe –o setproperty –pn days-to-show-new-icon –pv 0 –url