Aus der Praxis – Erstellung einer WCF WebService Factory für SharePoint 2010
avatar

In einem aktuelle Kundenprojekt war meine Aufgabe einen WCF WebService in SharePoint 2010 zu hosten, mit dem Dateien und Metadaten kopiert werden können.

Also setzte ich nach Standardvorgehen einen WebService um und erstellte eine SharePoint Solution:

  • Eine *.svc wird in den ISAPI Ordner von SharePoint bereitstellt, in der eine Service Klasse referenziert wird.
  • Die entsprechende Service-Klasse implementierte ich WCF-konform und stellte die DLL per Deployment im GAC bereit
  • Das generelle Vorgehen kann man z.B. hier oder hier sehr gut nachvollziehen.

Die ersten Tests verliefen erfolgreich, soweit war also alles gut.

Bei weiteren Tests allerdings fiel ein Problem auf. Sobald Dateien einer bestimmten Größe hochgeladen wurden, kamen diese Fehlermeldungen:

  • System.ServiceModel.ProtocolException: Der Remoteserver hat eine unerwartete Antwort zurückgegeben: (400) Bad Request.
  • System.ServiceModel.ProtocolException: Der Formatierer hat beim Deserialisieren der Nachricht eine Ausnahme ausgelöst: Fehler beim Deserialisieren von Parameter http://tempuri.org/:fileContent. Die InnerException-Nachricht war „Fehler beim Deserialisieren des Objekts „vom Typ System.Byte[]“. Die maximale Arraylänge (16384) wurde beim Lesen von XML-Daten überschritten. Dieses Kontingent kann durch Ändern der „MaxArrayLength“-Eigenschaft des beim Erstellen des XML-Lesers verwendeten „XmlDictionaryReaderQuotas“-Objekts erhöht werden. Zeile 1, Position 31317.“. Weitere Details finden Sie unter „InnerException“.

Diese Meldungen sagen aus, dass die Konfiguration des WCF Service so eingestellt ist, dass große Dateien nicht verarbeitet werden. Würde der WCF Service nun in einer eigenen Web-Anwendung ohne SharePoint gehostet werden, dann wäre es kein Problem die Konfiguration anzupassen – einfach in der web.config die entsprechenden Einstellungen vornehmen (unter anderem MaxArrayLength, MaxStringContentLength). Dieses Vorgehen wird häufig beschrieben, eine kurze google Suche (oder bing) hilft weiter.

In meinem Fall half mir das aber nicht weiter, denn in einer SharePoint Umgebung “mal eben so” die web.config anzupassen ist da nicht möglich.

Glücklicherweise fiel mir bei der Erstellung der Dateien des WCF Service der Inhalt der *.svc Datei auf:

<% @ServiceHost Debug="true"
    Service="HanseVision.FileMoveAction.FileMoveService, HanseVision.FileMoveAction, Version=1.0.0.0, Culture=neutral, PublicKeyToken=11e99323d9a1fb04"
    Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressBasicHttpBindingServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
    %>

Hier wird die Service Host Factory angegeben. Standardmäßig ist der Wert auf eine Klasse aus dem SharePoint Framework gesetzt. Doch man kann an dieser Stelle auch eine eigene Factory bauen, denn von der Klasse “MultipleBaseAddressBasicHttpBindingServiceHostFactory” kann man erben!

Der Code sah in meinem Fall dann so aus:

<% @ServiceHost Debug="true"
    Service="HanseVision.FileMoveAction.FileMoveService, HanseVision.FileMoveAction, Version=1.0.0.0, Culture=neutral, PublicKeyToken=11e99323d9a1fb04"
    Factory="HanseVision.FileMoveAction.ServiceHostFactory, HanseVision.FileMoveAction, Version=1.0.0.0, Culture=neutral, PublicKeyToken=11e99323d9a1fb04"
    %>

Die Implentierung meiner Service Host Factory:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Client.Services;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace HanseVision.FileMoveAction
{
    public class ServiceHostFactory : MultipleBaseAddressBasicHttpBindingServiceHostFactory
    {
        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            ServiceHost serviceHost = new MultipleBaseAddressBasicHttpBindingServiceHost(serviceType, baseAddresses);

            if (serviceHost != null)
            {
                serviceHost.Opening += new EventHandler(HostOpening);
            }

            return serviceHost;
        }

        void HostOpening(object sender, EventArgs e)
        {
            ServiceHost serviceHost = sender as ServiceHost;

            if (serviceHost != null && serviceHost.Description != null &amp;&amp; serviceHost.Description.Endpoints != null)
            {
                foreach (ServiceEndpoint endpoint in serviceHost.Description.Endpoints)
                {
                    if (endpoint != null && endpoint.Binding != null)
                    {
                        BasicHttpBinding basicBinding = endpoint.Binding as BasicHttpBinding;
                        if (basicBinding != null)
                        {
                            // configure binding settings
                            basicBinding.MaxReceivedMessageSize = Int32.MaxValue;
                            basicBinding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
                            basicBinding.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue;
                            basicBinding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
                        }
                    }
                }
            }
        }
    }
}

Nun wird der Service nicht mehr von der Standard SharePoint Factory erzeugt sondern von der von mir entwickelten.

Wird die Klasse aufgerufen, so wird mein Code ausgeführt, wenn die Verbindung zum Host geöffnet wird und ich habe somit die Kontrolle über die Konfiguration des Service-Endpunktes.

Ein Gedanke zu “Aus der Praxis – Erstellung einer WCF WebService Factory für SharePoint 2010
avatar

  1. Pingback: Aus der Praxis – Erstellung einer WCF WebService Factory für SharePoint 2010 - SharePoint Blogs in German - Bamboo Nation

Schreibe einen Kommentar