Probleme beim Veröffentlichen von ASP.Net kompatibler WCF Services

Wer, wie ich gerade, Probleme beim Veröffentlichen von WCF Services in einer ASP.Net Webseite hat sollte sich mal folgende zwei Artikel zu Gemüte führen:

http://social.msdn.microsoft.com/forums/en-US/wcf/thread/8c897f8e-2143-450e-a9f4-97d1f8702da7/

http://nimtug.org/blogs/damien-mcgivern/archive/2008/01/30/wcf-required-precompiled-asp-net-site-to-be-updatable.aspx

Der charmante und gleichzeitig nichts aussagende Fehler “Der Wert darf nicht NULL sein. Parametername: key” hat mich fast in den Wahnsinn getrieben. Gleichzeitig ist es in einer deutschen Entwicklungsumgebung gar nicht mal so einfach auf die richtige Lösung im Internet zu stoßen wenn man erstmal die Fehlermeldung richtig übersetzen muss ;-)

Meine Umgebung läuft auf Windows XP. Entwickelt wird mit Visual Studio 2008 und dem .Net Framework 3.5 SP1. Als IIS kommt die 5.1 Version die bei XP mitgeliefert wurde zum Einsatz. Nur um das mal erwähnt zu haben.

Einfach beim Veröffentlichen den Haken bei “Aktualisierbarkeit dieser vorkompilierten Site zulassen” (zumindest im deutschen Visual Studio) und schon laufen die Webservices ohne Fehlermeldung. Ob sich diese Variante für Anwendungen eignet, die ausgeliefert werden sollen, sei mal dahin gestellt. Für Intranetanwendungen aber ein interessanter Workaround.

Der andere Ansatz ist das Bearbeiten der .compiled Dateien die sich in der Webanwendung im bin Verzeichnis befinden sollten. In meinem Fall lautet der Name der Website in Visual Studio “Website”. Daher werden alle Service Dateien so kompiliert das sie auf “Website\Servicename.svc” verweisen. In den .compiled Dateien sieht das in etwa so aus:

<?xml version="1.0" encoding="utf-8"?>
<preserve resultType="5" virtualPath="/Website/ContentService.svc" [...] customString="/Website/ContentService.svc||ContentService|App_Web_c4jmapxp, [...]">
<filedeps>
<filedep name="/Website/ContentService.svc" />
</filedeps>
</preserve>

Anstatt “Website” sollte man hier nun den Namen des virtuellen Verzeichnisses, das man im IIS verwendet, wählen. Einfach wird es aber wenn man diesen einfach durch eine Tilde ~ ersetzt:

<?xml version="1.0" encoding="utf-8"?>
<preserve resultType="5" virtualPath="~/ContentService.svc" [...] customString="~/ContentService.svc||ContentService|App_Web_c4jmapxp, [...]">
<filedeps>
<filedep name="~/ContentService.svc" />
</filedeps>
</preserve>

Und schon funktioniert die Seite mit WCF Services auch ohne Updatefähigkeit in anderen IIS Umgebungen. Allerdings muss diese Ersetzung jedes mal von Hand in allen .compiled Dateien durchgeführt werden.

Update

Mein Script zum Suchen und Ersetzen ist fertig. Es besteht aus zwei Dateien. Zuerst die LoopThroughCompiledFiles.bat:

for /f %%a IN ('dir /b *.compiled') do call :Replace "%%a"
exit /b

:Replace
cscript //nologo ..\..\replace.vbs /Website ~ %~1
exit /b

Dieses Script macht folgendes: die for-Schleife geht durch den Schalter /f alle Dateien der folgenden dir Abfrage durch. Diese wiederum liefert nur Dateien mit der Endung .compiled. Der Schalter /b sorgt dafür das nur die Dateinamen mit Erweiterungen durchlaufen werden. Anschließend wird mit jedem Dateinamen der Replace Teil aufgerufen. Hier wird ein VB Script zum Suchen und Ersetzen aufgerufen. Wobei die Strings hier mit “/Website” und “~” hartcodiert sind. Mein Dank an dieser Stelle gilt James Welch für seinen hervorragenden Blog Eintrag “How to write a DOS batch file to loop through files“.

Nun hier natürlich auch noch der Code des VB Scripts replace.vbs, den ich von ghostmachine4 aus diesem Forumpost übernommen und angepasst habe (Danke auch dafür):

strOld = WScript.Arguments.Item(0)
strNew = WScript.Arguments.Item(1)
strF1 = WScript.Arguments.Item(2)
strF2 = strF1 & ".replaced"
Const ForWriting = 2

Set fso=CreateObject("Scripting.FileSystemObject")

Set f1 = fso.OpenTextFile(strF1)
Set f2 = fso.OpenTextFile(strF2, ForWriting, True)

Do Until f1.AtEndOfStream
   strLine = f1.ReadLine
   f2.WriteLine(Replace(strLine,strOld,strNew))
Loop

f1.Close
f2.Close

Set f1 = fso.GetFile(strF1)
f1.Delete()

Set f2 = fso.GetFile(strF2)
f2.Move(strF1)

Hier werden zuerst die Übergabeparameter ausgelesen und die entsprechenden Dateien angelegt und geöffnet. Dabei wird in eine temporäre Datei geschrieben. Die Originaldatei wird im Anschluss gelöscht und die temporäre Datei mit deren Dateiname versehen.


About this entry