ppedv 15 Jahre und Events

nicht ganz ohne Stolz feiern wir demnächst das 15jährige bestehen der ppedv AG. Dank smarter Kollegen und innovativer Microsoft Produkte sind wir heute einer der größten Microsoft Learning Partner im deutschsprachigen Bereich mit elf Niederlassungen. Berlin wird von mir am Donnerstag um 15:00 eröffnet. Jeder ist eingeladen vorbei zu kommen und das neueste Schulungscenter der ppedv kennen zu lernen. Um das zu feiern gibt es die 00001111, die 15 Jahres Konferenz von ppedv am 14.-15. Mai in Burghausen. Geballtes Wissen rund um Administration, Management und Entwicklung im Microsoft EcoSystem für nur 99,- €. Es feiern Partner, Kunden, Mitarbeiter und die Community. Wir haben für euch extra die besten Hotels von Burghausen zum Spitzenpreis reserviert. Sowohl die Betten als auch die Tickets werden schon knapp, also schnell zugreifen! Ein fast Welt Highlight ist die ADC++ im Alpenvorland. Vom 3-4. Mai treffen Sich dort die Welt Elite der C++ Community. IBM, INTEL, Microsoft. Daneben noch MVPs und weitere Praxis Experten. Gerade mit Window 8 hat Microsoft die C++ Entwickler wieder entdeckt. Aber auch klassisches wie parallele Programmierung, Boost oder MCF findet sich in der einzigartigen Agenda wieder. Wer sich für die Zukunft von C++ interessiert, muss da dabei sein. Im Juni veranstalten wir wieder gemeinsam mit Microsoft Österreich die größte deutschsprachige SharePoint Konferenz in Wien.  In den letzten beiden Jahren mit über 500 Teilnehmern und den wirklich wichtigen SharePoint Partnern und Lösungsanbietern, ist auch dieses Jahr von 25.-26. Juni der Treffpunkt für das SharePoint Business im gehobenen Ambiente des Hotels Savoyen. Hier schnell die Zimmer reservieren, die sind erfahrungsgemäß als erstes vergriffen. Besonderes Highlight dieses Jahr die Tour durch Microsoft Österreich – die neue Welt der Arbeit.

SecureStoreService in eigene SharePoint Lösungen einbinden

SharePoint 2010 bietet mit dem SecureStoreService eine sichere Möglichkeit Benutzerinformationen abzulegen. Aber wie werden diese in eigenen Anwendungen integriert? Im SecureStoreService können unter einem beliebigen Namen (dem Target Application ID) Benutzernamen und Passwörter abgelegt werden. Zusätzlich wird konfiguriert welche SharePoint Benutzer diese Credentials auslesen dürfen. Hier können einzelne Benutzer oder Benutzergruppen angegeben werden. Auslesen der Credentials mit Server API um den Secure Store Service im Code ansprechen zu können sind Verweise auf die microsoft.office.secureStoreService.dll sowie Microsoft.BusinessData.dll zu setzen. Beide Dlls liegen im ISAPI Verzeichnis des SharePoint Servers.   zunächst einmal der Code mit der Hauptfunktion. Die Übergabe der Target Application ID erfolgt im Parameter appId und die Rückgabe der Credentials als zwei out string Parameter. 1: public static void GetCredentials(string appId, out string user, out string PWD) 2: { 3: user = ""; 4: PWD = ""; 5: 6: ISecureStoreProvider provider = SecureStoreProviderFactory.Create(); 7:   8: ISecureStoreServiceContext providerContext = provider as ISecureStoreServiceContext; 9: providerContext.Context = SPServiceContext.GetContext(GetCentralAdminSite()); 10:   11: using (SecureStoreCredentialCollection creds = provider.GetCredentials(appId)) 12: { 13: if (creds != null) 14: { 15: foreach (SecureStoreCredential cred in creds) 16: { 17: switch (cred.CredentialType) 18: { 19: case SecureStoreCredentialType.UserName: 20: user = GetStringFromSecureString(cred.Credential); 21: break; 22: case SecureStoreCredentialType.Password: 23: PWD = GetStringFromSecureString(cred.Credential); 24: break; 25: } 26: } 27: } 28: } 29: } 30: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Credentials werden in einer Auflistung zurückgegeben. Anhand des Typs werden Benutzername und Password ausgelesen. Die gelieferten Typen hängen von der Konfiguration der Target Application im Secure Store Service ab. Möglich sind hier: Benutzername, Passwort, Pin, Windows-User, Windows-Passwort und generic. Die Daten werden als SecureString zurückgegeben. Mit der Funktion GetStringFromSecureString wird daraus ein normaler String. 1: private static string GetStringFromSecureString(SecureString secStr) 2: { 3: if (secStr == null) 4: { 5: return null; 6: } 7:   8: IntPtr pPlainText = IntPtr.Zero; 9: try 10: { 11: pPlainText = Marshal.SecureStringToBSTR(secStr); 12: return Marshal.PtrToStringBSTR(pPlainText); 13: } 14: finally 15: { 16: if (pPlainText != IntPtr.Zero) 17: { 18: Marshal.FreeBSTR(pPlainText); 19: } 20: } 21: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die so zurückgelieferten Information werden dann in weiterer Folge verwendet. Mein Szenario sieht den Zugriff auf einen SQL Server vor, also erstelle ich danach einen SQL Connection String. Mein nächster Gedankengang, war es ein Webpart zu schreiben, das die Usernamen und Passwörter der hinterlegten Credentials ausliest und anzeigt! Meine Vorstellung vom Secure Store Service war eine andere. Ich bin davon ausgegangen, dass die Informationen nicht mehr auslesbar sind. Beim Hinterlegen der Credentials wird ja auch folgende Meldung gezeigt: Es stimmt schon. Der Administrator kann die Credentials nicht mehr auslesen. Aber mit dem Server API ist es möglich!

Formular einer bestehenden SharePoint Liste via Code ändern

SharePoint 2010 nutzt Rendering Templates um die Formulare seiner Listen zur Laufzeit zu erstellen. Ein Entwickler kann weitere Templates erstellen und diese beispielsweise an ContentTypes zu definieren. An bestehenden Listen kann man zwar über das Property “TemplateName” den aktuellen Wert auslesen, eine Änderung ist aber nicht möglich. Dies erreicht man nur über die Anpassung der generierten ListFormWebParts in der Datenbank. Abhilfe schafft diese ExtensionMethod. public static class FormExtensions { public static void SetListFormTemplate(this SPForm form, string templateName) { if (form == null) throw new ArgumentNullException("form"); if (string.IsNullOrEmpty(templateName)) throw new ArgumentException("templateName required"); var web = form.ParentList.ParentWeb; var file = web.GetFile(form.Url); Debug.WriteLine("SetListFormTemplate: " + file.Url + " to " + templateName); web.AllowUnsafeUpdates = true; using (var lwpm = file.GetLimitedWebPartManager(PersonalizationScope.Shared)) { try { lwpm.Web.AllowUnsafeUpdates = true; var webPart = lwpm.WebParts.Cast<System.Web.UI.WebControls.WebParts.WebPart>().FirstOrDefault(x => x is ListFormWebPart); if (webPart != null) { var lfWebPart = webPart.WebBrowsableObject as ListFormWebPart; lfWebPart.TemplateName = templateName; lwpm.SaveChanges(lfWebPart); } lwpm.Web.AllowUnsafeUpdates = false; } finally { if (lwpm.Web != null) lwpm.Dispose(); } } file.Update(); web.AllowUnsafeUpdates = false; } }   Dieser Code kann dabei als PowerShell Skript verpackt werden, in eine Verwaltungsseite eingebettet oder in einem FeatureReceiver genutzt werden um die Templates via Code zu manipulieren. Hierzu muss einfach in einem Account der die nötigen Rechte besitzt die Extension Method an dem jeweiligen Formular aufgerufen werden. using (var site = new SPSite("http://win7-mp1/")) { var web = site.RootWeb; var list = web.Lists["Sample"]; var form = list.Forms[PAGETYPE.PAGE_DISPLAYFORM]; form.SetListFormTemplate("MichListForm"); }   Somit können auch Formulare von bereits bestehenden Listen leicht und sauber angepasst werden. Die RenderingTempaltes sollten dabei ganz normal via gemappte Folder nach “ControlTemplates” deployed werden.

SharePoint 2010 als Webpräsenz prüfen

Gerade bekomme ich die Frage rein wie man die Geschwindigkeit ganz einfach einer SharePoint Website prüfen kann. Dies soll eine Relevanz auf das Rating der Suchergebnisse bei Google und Bing haben, ist also ein SharePoint SEO Thema. Ich weis nicht ob unsere Trainer im Kurs darauf hinweisen, aber ist eigentlich so einfach, das es schon weg tut. Wenn man den Internet Explorer 9 verwendet. Einfach F12 drücken, Reiter Netzwerk und Aufzeichnung starten. Kurze Analyse. Alles was eine 404 Meldung hat in der Spalte Ergebnis muss korrigiert werden. Rechts sieht man eine grüne Linie. Ab diesem Zeitpunkt ist das DOM geladen und kann vom Browser gerendert werden kann bzw. JavaScript mit dem DOM arbeiten kann (OnDomContentLoaded Event). Ab der roten Linie ist auch das Event Loaded gefeuert und das finale Rendering des HTML möglich. Das kann natürlich sein das eine selten aufgerufene Unterseite in SharePoint erst kompiliert werden muss. Im Zweifel also die Seite per F5 nochmal aufrufen, dann sieht man auch ob und wie Caching wirkt. Bilder die im Cache liegen werden vom Server nicht mehr ausgeliefert sondern statt dessen eine 304 geschickt. Ein gängiger Fehler ist, das Bilder einfach hochgeladen werden ohne sie vorher auf die Formatgröße zu reduzieren in der sie letztlich gebraucht werden. SharePoint kann nicht automatisch Thumbnails in der passenden Width und Height erzeugen.

Sitemap für SharePoint 2010

Im Rahmen meiner SEO Tätigkeiten für die Minerva Website (die auf Sharepoint 2010 Foundationbasiert) bin ich über die Google und Bing Webmaster Tools gestolpert. Nettes Zeugs, das ich eventuell später einmal behandle.  Jedenfalls kann man dort eine Sitemap angeben und die Spider der Suchmaschinen davon überzeugen die eigene Website zu crawlen. Es gibt eine Menge Online Tools die das tun, aber SharePoint hat eben auch viele URL’s die man nicht im Index finden möchte. Nun hat mein Kollege Michael (ein SharePoint Guru) mir geholfen auf Basis des Blog Eintrages ein Powershell Script zu erstellen das sozusagen täglich neu und automatisiert die Sitemap einer SharePoint Website erstellt. Das Script muss mit der Endung PS1 gespeichert werden und kann über den Zeitplandienst von Windows regelmäßig gestartet werden Add-PSSnapin microsoft.sharepoint.powershell -ErrorAction SilentlyContinue function New-SPSiteMap { param($SavePath="C:\inetpub\wwwroot\wss\VirtualDirectories\minerva1.ppedv.de80\SiteMap.xml", $Url="http://minerva.ppedv.de") $site = Get-SPSite $url $list = $site.AllWebs | ForEach-Object -Process { $_.Lists } | ForEach-Object -Process {$_.Items} | ForEach-Object -Process { $_.Web.Url.Replace(" ","%20") + "/" + $_.url.Replace(" ","%20")} #excludes directories you don’t want in sitemap. you can put multiple lines here: $list= $list | ? {$_ -notmatch "_catalogs"} $list=  $list | ? {$_ -notmatch "Cache%20Profiles"}        $list=  $list | ? {$_ -notmatch "Reports%20List"}  $list= $list | ? {$_ -notmatch "Reporting%20Templates"}          $list= $list | ? {$_ -notmatch "Reporting%20Metadata"}             $list=  $list | ? {$_ -notmatch "Long%20Running%20Operation%20Status"}        $list=  $list | ? {$_ -notmatch "Relationships%20List"}        $list=  $list | ? {$_ -notmatch "ReusableContent"}        $list=  $list | ? {$_ -notmatch "Style%20Library"}        $list=  $list | ? {$_ -notmatch "PublishingImages"}        $list=  $list | ? {$_ -notmatch "SiteCollectionDocuments"}        $list=  $list | ? {$_ -notmatch "Lists/"}        $list=  $list | ? {$_ -notmatch "Documents/"}        $list=  $list | ? {$_ -notmatch "PublishedLinks"}        $list=  $list | ? {$_ -notmatch "WorkflowHistory"}        $list=  $list | ? {$_ -notmatch "WorkflowTasks"} } function New-Xml{      param($RootTag="urlset",$ItemTag="url", $ChildItems="*", $SavePath="Pfad\SiteMap.xml")    Begin {        $xml="<?xml version=""1.0"" encoding=""UTF-8""?>        <urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">"        }    Process {        $xml += " <$ItemTag>"        foreach ($child in $_){            $Name = $child            $xml += " <$ChildItems>$child</$ChildItems>"        }        $xml += " </$ItemTag>"        }    End {        $xml += "</$RootTag>"        $xmltext=[xml]$xml        $xmltext.Save($SavePath)    }} New-SPSiteMap

301 statt 302 und dann klappt SharePoint auch mit Google?

Man sagt, das google und auch andere Suchmaschinen Seiten nicht indizieren, wenn auf diese per HTTP Status Code 302 umgeleitet wird. Also der ASP.NET Klassiker Response.redirect(“http://minerva.ppedv.de”) bewirkt das diese Seite nicht im Index landet. Außer es gibt woanders einen direkten Hyperlink darauf. In ASP.NET 4.0 gibt es einen neuen Redirect Befehl Response.RedirectPermanent der nun einen 301 Code liefert. Nun ist SharePoint an dieser Stelle eine Zicke. Üblich SharePoint Sites sind so konfiguriert, das die Links z.b. in der Navigation auf Sitepages in der Form http://minerva.ppedv.de/ erfolgen. Beim Aufruf wird umgeleitet per 302 auf http://minerva.ppedv.de/SitePages/Homepage.aspx Es gibt im Web einige (komplizierte) Möglichkeiten die beschreiben wie man per Routing und URLRewrite eine passende Umleitung vornimmt. Ich habe mich entschieden ein HTTP Modul zu schreiben und mich einfach in die IIS Pipeline einzuklinken. Ob das gut oder schlecht ist, keine Ahnung. using System; using System.Web; public class r301: IHttpModule { public r301() { } public void Dispose() { } public void Init(HttpApplication context) { context.BeginRequest += (new EventHandler(this.Application_BeginRequest)); context.EndRequest += (new EventHandler(this.Application_EndRequest)); } private void Application_BeginRequest(Object source, EventArgs e) { } private void Application_EndRequest(Object source, EventArgs e) { HttpApplication application = (HttpApplication)source; HttpContext context = application.Context; if (context.Response.StatusCode.ToString() == "302") { context.Response.Status = "301 Moved Permanently"; context.Response.Flush(); context.Response.End(); } } } Das Modul wird als CS im APP_Code Verzeichnis abgelegt und dann im IIS Manager( man kann’s auch per Web.Config tun) von mir registriert. Für den Test nehme ich die IE9 Developer Tools (F12) und starte die Netzwerkaufzeichung. .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

SEO für SharePoint 2010

Unser Tochterunternehmen Minerva (Spezialist für Microsoft Zertifizierung Camps) hat das Abenteuer Web Präsenz mit SharePoint Foundation gestartet. Die ersten Ergebnisse sind leider ernüchternd. Weder bei BING noch Google tauchen wir unter den ersten 100 Treffern in der Ergebnisliste auf. Übliche Schlüsselwörter für den Test sind z.b. Microsoft Zertifizierung Herausforderung Teil 1 ist der Page Title. Der geht ja gar nicht. Üblicherweise Home- Homepage 1. Wenn man den Pagetitle ändern möchte kann, man natürlich mit Sharepoint Designer in jede Page einzeln gehen und dort den Titel im PlaceHolderPageTitle ändern ( statt dem EnocdedLiteral und ProjectProperty)   Dazu muss man aber mit der Anweisung erweiterter Modus eine lokale Kopie des Template Inhalts holen. Mein Kollege Michael meinte aus Performance gründen zu vermeiden. Nun ist meine Idee anstatt dem Page Title automatisch die Description anzuzeigen. Diese erscheint in der Breadcrump Navigation von SharePoint und dann auch im Page Title im Browser Reiter. Da die Minerva Website Inhalte per Wiki Realisiert sind, findet sich das Template in  C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\Documenttemplates\wkpstd.aspx Ziemlich am Anfang habe ich dann den ASPX Code wie folgt verändert <asp:Content ContentPlaceHolderId="PlaceHolderPageTitle" runat="server"> <SharePoint:ProjectProperty Property="Description" runat="server"/> </asp:Content> Nun haben wir wenigstens einen sinnvollen Seitentitel und damit Futter für die Suchmaschine.

Listenberechtigung eines Users mit Client Object Model abfragen

Oftmals ist es notwendig, zu überprüfen ob ein Benutzer in SharePoint berechtigt ist, einen Listeneintrag zu erstellen oder ein Dokument hochzuladen. Die Aufgabenstellung kann auch in Klient-Anwendungen vorkommen. Das Client Object Model von SharePoint 2010 bietet auch hierfür Funktionen an. EffectiveBasePermission Eine Klient-Listen Klasse bietet mit “EffectiveBasePermission” den Zugriff auf die Persmissions des Benutzers. liste.EffectiveBasePermissions.Has(PermissionKind.AddListItems) Die Methode “Has” überprüft ob eine bestimmte Permission vorliegt. Das enum “PermissionKind” bietet alle in SharePoint definierten Persmissions. Die genaue Bedeutung kann im MSDN nachgelesen werden. Der Sourcecode prüft für den aktuellen User, ob die Berechtigung “AddListItems” gesetzt ist: 1: public static bool SchreibRechtAufListe(string listenName) 2: { 3: ClientContext ctx = new ClientContext(url); 4: ctx.Credentials = System.Net.CredentialCache.DefaultCredentials; 5: List liste = ctx.Web.Lists.GetByTitle(listenName); 6: ctx.Load(liste, L => L.EffectiveBasePermissions); 7: ctx.ExecuteQuery(); 8: return liste.EffectiveBasePermissions.Has(PermissionKind.AddListItems); 9: }

Die unendlichen Möglichkeiten des Client Object Models

Das Client Objekt Model von SharePoint 2010 ermöglicht die Entwicklung von SharePoint-Anwendungen die nicht im Kontext des SharePoint Servers laufen. Im wesentlichen bietet das COM (hatten wir diese Abkürzung nicht schon mal?) die Funktionalität des Server API, nur eben für Remote-Anwendungen. So ist es möglich Listen und Bibliotheken anzulegen, zu verändern, Daten zu schreiben und noch weitere Änderungen am SharePoint Server vorzunehmen… Wie mir unlängst beim Durchstöbern der Funktionen aufgefallen ist, mit dem Client Objekt Model können auch die Menüs erweitert werden! Gut, einen konkreten Anwendungsfall habe ich nun hierfür nicht. Aber muss immer gleich auf den ersten Blick erkennbar sein wofür eine Funktion benötigt wird? Evtl. kann ich in ein paar Monaten von einer Anwendung berichten. Menüeintrag für einen EditControlBlock erstellen Normalerweise werden Erweiterungen der SharePoint Menüs über Feature-Dateien realisiert. Für die Erweiterung der Menüs kommt ein CustomAction-Feature zur Anwendung. Jedoch kann dieses auch per Client API erstellt werden. Die CustomActions werden im COM als UserCustomAction-Ojekte angesprochen. erster Schritt: wir laden die UserCustomActions einer Liste in einen Client-Kontext. 1: ClientContext ctx = new ClientContext("http://nbmag/Dev"); 2: List liste = ctx.Web.Lists.GetByTitle("Personen"); 3:  4: ctx.Load(liste.UserCustomActions); 5: ctx.ExecuteQuery();   Danach empfiehlt es sich eine bereits angelegt UCA zu löschen. In diesem Fall wird die UCA anhand des Titels identifiziert. 1: foreach (UserCustomAction uca in liste.UserCustomActions) 2: { 3: if (uca.Title == "Brief schreiben") 4: { 5: uca.DeleteObject(); 6: break; 7: } 8: } Der abschließende Schritt ist nun das Anlegen der neuen UserControlAction. Die Einstellungen entsprechen jenen, die in einer Feature Datei ebenfalls zu setzen sind. (Siehe MSDN)  Durch die Eigenschaften “Location” und “Group” wird das zu erweiternde Menü definiert. Eine Liste der möglichen Einträge und deren Bedeutung liefert das ebenfalls MSDN. 1: UserCustomAction act = liste.UserCustomActions.Add(); 2: act.Location = "EditControlBlock"; 3: act.Sequence =100; 4: act.Title ="Brief schreiben"; 5: act.Url = "/_layouts/WriteLetter.aspx"; 6: act.ImageUrl = "/_layouts/images/mail_add.png"; 7: act.Update(); 8: ctx.ExecuteQuery(); In diesem Beispiel wird eine Applikation-Page im Layouts Verzeichnis aufgerufen. Und hier das Ergebnis der wenigen Zeilen Code:

SharePoint Dokumentbibliothek–automatische Thumbnails

Heute wurde mir in einer SharePoint Schulung eine Anforderung präsentiert: Beim Upload eines Bildes in eine Dokumentbibliothek soll automatisch ein Thumbnail generiert werden. Ein einfacher Lösungsansatz ist es, einen EventReceiver zu erstellen. Dieser wird nach dem Hochladen einer Datei ausgelöst und konvertiert die hochgeladene Datei in ein kleineres Format. SourceCode 1: public class PictureUploadEventReceiver : SPItemEventReceiver 2: { 3: /// <summary> 4: /// An item was added. 5: /// </summary> 6: public override void ItemAdded(SPItemEventProperties properties) 7: { 8: try 9: { 10: SPListItem item = properties.ListItem; 11: if (item.File.Name.ToLower().EndsWith("jpg") && !item.File.Name.ToLower().StartsWith("small_")) 12: { 13: Stream stream = properties.ListItem.File.OpenBinaryStream(); 14: Image pic = Image.FromStream(stream); 15:  16: Image small = pic.GetThumbnailImage(100, 100, new Image.GetThumbnailImageAbort(CallBack), IntPtr.Zero); 17:  18: MemoryStream str = new MemoryStream(); 19: small.Save(str, ImageFormat.Jpeg ); 20: 21: Byte[] content = new Byte[str.Length]; 22:  23: content = str.ToArray(); 24:  25: SPList bibliothek = item.ParentList; 26: string newUrl = bibliothek.RootFolder.Url + "/small_" + item.File.Name; 27:  28: bibliothek.RootFolder.Files.Add(newUrl, content); 29: bibliothek.Update(); 30: } 31: } 32: catch (Exception ex) 33: { 34: 35: } 36: base.ItemAdded(properties); 37: } 38:  39: public bool CallBack() 40: { 41: return true; 42: } Zunächst wird in Zeile 11 der Dateiname der hochgeladenen Datei geprüft. Danach wird aus der Datei ein Image Objekt erstellt. Dieses bietet die Funktion GetThumbnailImage. Die Paramter der Funktion sind die neue Abmessung, sowie eine Callback-Funktion die laut MSDN Dokumentation nicht aufgerufen wird, aber anzugeben ist…  Nun gut, ich nehme es mal zur Kenntnis und denke nicht weiter über den Grund nach Dann wird der Bildinhalt als Byte-Array gespeichert.  In Zeile 26 erstelle ich den neuen Dateiname erstellt. In diesem Beispiel wird dem neuen Dateinamen “small_” vorangestellt. Im Anschluss  noch das Byte Array als neue Datei in die Dokumentbibliothek hochladen. und der SourceCode ist fertig. Deployment Um den EventReceiver in SharePoint zu installieren ist noch eine Elements_Datei notwendig. Zum Glück legt Visual Studio 2010 diese Datei automatisch an, sobald ein EventReceiver erstellt wird. 1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <Receivers ListTemplateId="101"> 4: <Receiver> 5: <Name>PictureUploadEventReceiverItemAdded</Name> 6: <Type>ItemAdded</Type> 7: <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly> 8: <Class>PictureConverter.PictureUploadEventReceiver.PictureUploadEventReceiver</Class> 9: <SequenceNumber>10000</SequenceNumber> 10: </Receiver> 11:  12: </Receivers> 13: </Elements> In diesem Fall wird der EventReceiver an eine Liste vom Typ 101 (also Dokumentbibliothek) gebunden. Somit wird der EventReceiver nun bei allen Dokumentbibliotheken ausgelöst. Ok, EventReceiver fertig und kann nun auch deployed werden.