July 2003 - Posts

Bei der Datenbankprogrammierung ist ja immer wieder eine Frage, ob denn der Connectionstring gespeichert werden sollte. Die Optionen reichen von einer Integration in den Code über .NET Enterprise Services Constructor Strings bis zu verschlüsselten Dateien.

Meine Lieblingsoption vor allem während des Prototypings ist allerdings eine UDL-Datei. Man kann sie leicht erzeugen (Datei mit Extension .udl erzeugen und doppelklicken; dann erscheint ein Wizard, um den Connection String zusammenzustellen) und man kann sie leicht an eine Connection übergeben:

New OleDbConnection("file name=myconnection.udl")

So muss ich nicht die ganzen Einstellungen für die unterschiedlichen OLEDB Provider im Kopf haben. Sehr bequem!

Wenn die UDL-Datei dann bei WinForms-Anwendungen im Anwendungsverzeichnis liegt, ist die Welt auch in Ordnung.
Liegt sie dort nicht, muss man einen Pfad voranstellen, z.B. c:\database\myconnection.udl.

Besonders lästig ist es nun, wenn man Connections mit dem selben Code in WinForms und ASP.NET Anwendungen erzeugen will. Bei WinForms ist kein Pfad nötig, bei ASP.NET aber doch. Denn dort ist das Anwendungsverzeichnis das System32-Verzeichnis - und dort will man keine UDL-Dateien ablegen.

Das Problem lässt sich aber durchaus einfach lösen. Zunächst sollte die Erzeugung einer Datenbankverbindung ja ohnehin immer in eine zentrale Methode verpackt werden, z.B. CreateConnection(). In ihr kann dann automatisch ermittelt werden, ob der Code in einer ASP.NET Anwendung läuft:

Imports System.Web

Public Function CreateConnection() As OleDbConnection
    Dim path As String
    Dim ctx As HttpContext = HttpContext.Current
    If Not ctx Is Nothing Then path = ctx.Server.MapPath(".") & "\"
    Return New OleDbConnection(String.Format("file name={0}myconnection.udl", path))
End Function

Wenn ein HttpContext-Objekt existiert, dann wird die Methode in einer ASP.NET Anwendung aufgerufen. Und dann lässt sich das Anwendungsverzeichnis über Server.MapPath(".") ermitteln. Die Connectionerzeugung ist damit unabhängig vom Host für den Code.

Dieses Vorgehen funktioniert natürlich auch, wenn ein Connectionstring an einem anderen Ort liegt und z.B. nur einen Datenbankpfad enthält. Im Connectionstring sollte dann nur auch ein Platzhalter wie "{0}" für den Pfad vorgesehen sein.

Gestern habe ich mich ein paar Stunden mit WinForms Data Binding auseinandersetzen müssen. Puh, keine so leichte Sache. Microsoft hat es bestimmt gut gemeint dabei - aber ob wirklich am Ende etwas rundum Gutes herausgekommen ist? Hm... Ich weiß nicht, ich weiß nicht. Es funktioniert zwar auf weite Strecken ganz nett, aber so richtig reibungslos ist es in nicht trivialen Szenarien nicht. Trotzdem habe ich mich damit durchaus angefreundet und es erleichtert die Arbeit gerade beim Prototyping.

Eine meiner Erkenntnisse ist z.B.: Übersichts- und Bearbeitungsfenster müssen an dieselbe Datenquelle gebunden werden, wenn Data Binding Spaß machen soll.

Ok, was meine ich mit "Übersichts- und Bearbeitungsfenster"? Ein typisches Szenario bei der Datenbankprogrammierung ist, eine Liste von Datensätzen in einem Fenster anzuzeigen (Übersicht) und den Anwender zur Bearbeitung daraus einen Datensatz auswählen zu lassen.

Die Bearbeitung findet dann in einem eigenen Fenster (Bearbeitung) statt. Die Datenbindung im Übersichtsfenster ist einfach und wie zu erwarten:

Private _ds as DataSet
Private _dv as DataView
...

Dim conn As New SqlConnection("...;database=northwind")
Dim adap As New SqlDataAdapter("select ..., companyname from customers", conn)

_ds = New DataSet
adap.Fill(_ds, "customers")

_dv = New DataView(_ds.Tables("customers"))
DataGrid1.DataSource = _dv

Daten laden, Daten binden. Fertig. Allerdings: Gebunden werden sollte immer ein expliziter DataView. Man kann zwar auch eine DataTable oder ein DataSet binden, aber meine Empfehlung ist, einen eigenen DataView zu erzeugen.

Damit hat man die Möglichkeit, z.B. jederzeit die Bearbeitung der Übersicht einzuschränken, z.B.

_dv.AllowEdit = false

Außerdem hat man damit ein Objekt in der Hand, das auch bei weiteren Bindungen genutzt werden kann. Wenn man z.B. Daten der Tabelle im Übersichtsfenster auch nochmal in einer Textbox anzeigen möchte, dann ist das kein Problem:

TextBox1.DataBindings.Add("text", _dv, "companyname")

Entscheidend ist, dabei auch wieder _dv als Quelle anzugeben, um die Zahl der CurrencyManager zu minimieren. Für DataGrid1 und TextBox1 wäre in diesem Fall derselbe CurrencyManager zuständig, d.h. wenn man im Grid weiterblättert, ändert sich auch die Anzeige in der Textbox.

Soweit so gut. Das hatte ich gestern im Grunde auch gemacht. Ein Problem trat aber auf, als ich dann ein Bearbeitungsfenster öffnen wollte. Was habe ich da so ganz naiv gemacht? Im Übersichtsfenster habe ich den zu bearbeitenden Satz ermittelt und ihn an das Bearbeitungsfenster übergeben:

Dim frmBearb As New Bearbeitungsfenster()
Dim bm As BindingManagerBase = Me.BindingContext(_dv)
Dim rv As DataRowView = bm.Current
frmBearb.ShowDlg(rv)

Das schien mir völlig plausibel. Warum sollte das Bearbeitungsfenster mehr kennen, als den zu bearbeitenden Satz? Im Bearbeitungsfenster habe ich dann wie folgt gebunden:

Private _rv as DataRowView

Public Sub ShowDlg(ByVal rv As DataRowView)
    TextBox1.DataBindings.Add("text", rv, "companyname")
    _rv = rv
    Me.ShowDialog()
End Sub

Auch kein Problem. Und alles wäre gut geblieben, wenn ich denn den Inhalt der anzuzeigenden Spalte companyname nur durch Eingabe in die Textbox im Bearbeitungsfenster hätte verändern wollen.

Ich wollte aber etwas anderes! Einige gebundene Spalten sollten durch Code verändert werden, z.B.

_rv("companyname") = "..."

Das funktioniert auch, man sieht die Änderung in der Tabelle des Übersichtsfensters - aber sie erscheint nicht (!) in der Textbox des Bearbeitungsfenster. Warum das so ist, ist mir nicht wirklich klar.

Dafür habe ich aber einen Workaround gefunden, den ich unterm Strich ohnehin für den konsequenteren Weg halte. Das Übersichtsfenster übergibt nicht mehr die in Bearbeitungs befindliche Tabellenzeile, sondern die Datenquelle, an die das Grid bebunden ist inkl. der Position, auf der es gerade steht:

Dim frmBearb As New Bearbeitungsfenster()
Dim bm As BindingManagerBase = Me.BindingContext(_dv)
frmBearb.ShowDlg(_dv, bm.position)

Und das Bearbeitungsfenster bindet an dieselbe Datenquelle wie das Übersichtsfenster:

Public Sub ShowDlg(ByVal dv as DataView, index as Integer)
    TextBox1.DataBindings.Add("text", dv, "companyname")
    me.BindingContext(dv).Position = index
    _rv = me.BindingContext(dv).Current
    Me.ShowDialog()
End Sub

Damit dann aber auch dort der richtige Satz angezeigt wird, muss noch der CurrencyManager auf den selben Satz wie das Übersichtsfenster gestellt werden.

Das mag etwas weniger intuitiv sein, als den zu bearbeitenden Satz zu übergeben, aber der Effekt ist der selbe und dieser Weg bietet mehr Freiheiten. Jetzt ist die Veränderung der angezeigten Daten auch über eine Zuweisung an den DataRowView möglich.

Fazit: Beim Data Binding sollte man sorgfältig darauf achten, dass alle Controls, die grundsätzlich an der selben Datenquelle hängen, auch wirklich über das selbe Objekt (am besten bzw. in den meisten Fällen ein DataView) gebunden werden.

Mein kleines Tool zur Vereinfachung des Umgangs mit dem GAC hat mich nicht in Ruhe gelassen. Ein paar Kleinigkeiten wollte ich noch polieren - aber vor allem wollte ich eine automatisches Registrierung von Serviced Components. Jetzt habe ich endlich Zeit gefunden, alles einzubauen. Der komplette Quelltext ist unten zu finden.

Verbesserungen/Veränderungen:

-Nach einem Aufruf eine externen Programms wie gacutil.exe wird jetzt auf dessen Prozessende gewartet. Die gesamte Logik des Tools hier läuft damit sequenziell ab; deshalb ist auch am Ende ein Schlussmeldung und ein Warten auf Tastendruck möglich. Zum Warten auf ein Prozessende siehe auch meinen Blog-Eintrag "Hürde beim Warten auf ein Prozessende".

-Ich habe den Code etwas refaktorisiert, indem ich Teile rausgezogen habe in eigene Routinen (Affect...()). Die Main()-Methode ist dadurch übersichtlicher geworden und Code zu einem Thema wie GAC-Registrierung usw. steht nur noch an einem Ort. Das Buch "Refactoring. Wie Sie das Design vorhandener Software verbessern" von Martin Fowler sei an dieser Stelle nochmals empfohlen.

Neuerungen:

-Die zu behandelnde Assembly wird daraufhin geprüft, ob sie einen .NET Enterprise Services (ES) Dienst darstellt. Mein Kriterium ist das Assembly-Attribut ApplicationActivation. Ist es vorhanden, wird im Menü die Option geboten, die Assembly bei den ES zu (de)registrieren. Ich denke, dass diese Annahme ausreichend ist, denn zur automatischen Registrierung muss regsvcs.exe bekannt gemacht werden, in welcher COm+ Applikation die DLL registriert werden soll. Da die meisten Komponenten applikationsspezifisch sein werden, ist es allemal während der Entwicklung bequem, ihre Applikationszugehörigkeit in ihnen mit dem Attribut zu verdrahten.

-Wenn die Assembly bei COM+ (de)registriert werden soll, dann ruft das Tool regsvcs.exe auf. Solange Sie COM+ Library-Applikationen entwickeln, ist eine solche explizite Registrierung nicht immer nötig. VS.NET macht das für sie. Bei Server-Applikationen allerdings schlägt der Automatismus fehl. Und Server-Apps sind z.B. für Queued Components zwingend notwendig. Hier leistet das Tool wertvolle Hilfe, wenn man neben VS.NET noch einen Windows Explorer mit dem Verzeichnis der gerade in Entwicklung befindlichen Anwendung offen hat:

1. Anwendung inkl. Serviced Component Assembly übersetzen.
2. Im Windows Explorer das Tool auf der Serviced Component Assembly aufrufen und "14" eingeben (registrieren der Assembly im GAC und bei COM+).
3. Anwendung in VS.NET starten und testen.
4. Vor weiterarbeit an der Serviced Component die Assembly mit dem Tool und "24" aus dem GAC nehmen und bei COM+ deregistrieren.

Der mangelnde Automatismus von VS.NET fällt da fast gar nicht mehr auf ;-)

Enjoy!

imports Microsoft.Win32

Module Module1

    Sub Main(args() as string)
        Dim shellKey as RegistryKey = Registry.ClassesRoot.CreateSubKey("dllfile\Shell\GacUtils\Command")
        shellKey.SetValue("", string.Format("{0} ""%1""", Reflection.Assembly.GetExecutingAssembly.Location))

        shellKey = Registry.ClassesRoot.CreateSubKey("dllfile\Shell\ILdasm\Command")
        shellKey.SetValue("", string.Format("{0} ""%1""", "ildasm.exe"))
        shellKey = Registry.ClassesRoot.CreateSubKey("exefile\Shell\ILdasm\Command")
        shellKey.SetValue("", string.Format("{0} ""%1""", "ildasm.exe"))

        if args.Length <> 1 then
            msgbox("Usage: GacUtils <DLL assembly filename>", MsgBoxStyle.Information)
            exit sub
        End If

        dim ass as Reflection.Assembly = Reflection.Assembly.LoadFrom(args(0))
        dim assName as String = ass.FullName.Split(",")(0)

        dim isServicedComponent as Boolean
        isServicedComponent = ass.GetCustomAttributes(gettype(System.EnterpriseServices.ApplicationActivationAttribute), false).Length > 0

        dim menu as String = "1=Register in GAC,2=Unreg from GAC,3=Affect 'Add references' dialog"
        if isServicedComponent then menu &= ",4=Affect COM+ catalog"

        Console.WriteLine(menu.Replace(",", vbcrlf))
        Console.Write("  Multiple commands allowed: ")
        dim cmds as String = console.ReadLine
        dim p as Process

        if cmds.IndexOf("2") >= 0 then
            if cmds.IndexOf("4") >= 0 and isServicedComponent then AffectComPlus(false, assName)
            AffectGAC(false, assName)
            if cmds.IndexOf("3") >= 0 then AffectAddRefListing(assName)
        End If

        if cmds.IndexOf("1") >= 0 then
            AffectGAC(True, assName)
            if cmds.IndexOf("4") >= 0 and isServicedComponent then AffectComPlus(True, assName)
            if cmds.IndexOf("3") >= 0 then AffectAddRefListing(assName, IO.Path.GetDirectoryName(args(0)))
        End If

        if cmds = "3" then AffectAddRefListing(assName, IO.Path.GetDirectoryName(args(0)))
        if cmds = "4"  and isServicedComponent then AffectComPlus(True, assName)

        console.WriteLine
        console.Write("<enter> to quit... ")
        console.readline
    End Sub


    private sub AffectGAC(register as Boolean, assemblyName as String)
        dim fmt as String = iif(register, "/i {0}.dll", "/u {0}")

        dim psi as New ProcessStartInfo("gacutil.exe", string.Format(fmt, assemblyName))
        psi.UseShellExecute = false

        dim p as Process = Process.Start(psi)
        p.WaitForExit
    End Sub

    private sub AffectComPlus(register as Boolean, assemblyName as String)
        dim fmt as String = iif(register, "{0}.dll", "/u {0}.dll")

        dim psi as New ProcessStartInfo("regsvcs.exe", string.Format(fmt, assemblyName))
        psi.UseShellExecute = false

        dim p as Process = Process.Start(psi)
        p.WaitForExit
    End Sub

    private sub AffectAddRefListing(assemblyName as String, optional path as string = "")
        console.WriteLine
        if path <> "" then
            console.WriteLine("Registering directory {0} for inclusion in 'Add references'", path)
            dim refKey as RegistryKey = Registry.LocalMachine.CreateSubKey("Software\Microsoft\.NETFramework\AssemblyFolders\" & assemblyName)
            refKey.SetValue("", path)
        else
            console.WriteLine("Unregistering directory {0} from 'Add references'", path)
            Registry.LocalMachine.DeleteSubKey("Software\Microsoft\.NETFramework\AssemblyFolders\" & assemblyName)
        End If
    End Sub
End Module

Es ist schon merkwürdig: Da sind wir auf der einen Seite ganz glücklich darüber, dass die CLR eine Ebene zwischen unseren Sourcecode und das Betriebssystem zieht. Wir begrüßen die Abstraktion von der Maschine. Und auf der anderen Seite haben wir uns (oder zumindest die ehemaligen VB6-Entwickler) seitdem ungleich öfter mit Assemblercode auseinandergesetzt. Zumindest für mich ist IL-Assembler seit meinen Z80-Zeiten der erste Assemblercode, den ich mir oft und gern anschaue.

Woran das liegt? An den Metadaten, die neben dem Code in den Assemblies stecken. Ein Blick in eine Assembly mit ILdasm zeigt mir sofort, wasfüreine Programmierschnittstelle sie veröffentlicht. Aber es liegt auch daran, dass IL-Code ungleich verständlicher ist. Es ist eine stackbasierte Sprache und im IL-Assemblercode sind Methodenaufrufe gut lesbar. Gerade für Assemblies, zu denen ich keinen Quellcode habe, ist also der Blick in den IL-Code sehr erhellend, was ihre Funktionsweise angeht.

Microsoft liefert zum Glück dafür ja auch ein brauchbares Tool mit. Den IL-Disassembler ILdasm.exe. Damit jedoch eine Assembly anzuschauen, ist etwas umständlich: Entweder öffne ich ILdasm über das Programmmenü und wähle über File|Open eine Assembly aus, oder ILdasm ist offen und ich ziehe eine Assembly per Drag&Drop darauf. Eine "normale" Funktionalität, aber eine, wie ich finde, umständliche. Denn was möchte ich meist tun? Ich sehe eine Assembly im Windows Explorer und möchte ILdasm sofort damit öffnen. Ich möchte keinen Umweg gehen und das Verzeichnis nochmal in seinem Open-Dialog auswählen und ich möchte mir auch nicht die Finger bei Drag&Drop brechen.

Die Lösung wäre, ich öffne ILdasm via Kontextmenü der Assembly im Windows Explorer. Dafür muss ILdasm aber als Shell-Kommando für DLL- und EXE-Dateien registriert sein. Hier ein reg-Skript, das genau das leistet:

[HKEY_CLASSES_ROOT\exefile\shell\ILdasm\Command]
@="ildasm.exe \"%1\""
[HKEY_CLASSES_ROOT\dllfile\Shell\ILdasm\Command]
@="ildasm.exe \"%1\""

Skriptcode in eine reg-Datei kopieren und die Datei ausführen. Fertig. Fortan gibt es einen neuen Menüpunkt im Kontextmenü von DLLs und EXEs.

In meinem Code von gestern habe ich gacutil.exe gestartet, um die (De)Registrierung einer Assembly im GAC vorzunehmen:

dim psi as New ProcessStartInfo("gacutil.exe", string.Format("/i {0}.dll", assName))
psi.UseShellExecute = false
p = new Process
p.Start(psi)

Das war schön und gut und hat funktioniert. Zwischendurch hatte ich dann mal gedacht, ich sollte vielleicht nach Start() darauf warten, dass der Prozess beendet wird. Also habe ich

p.WaitForExit()

danach eingesetzt. Allerdings hat das immer zu einem Fehler geführt :-( Da es nicht so wichtig war, habe ich es dann 'raus genommen.

Letztlich hat es mich aber nicht in Ruhe gelassen. Warum sollte so ein simpler Aufruf nicht funktionieren? Was konnte ich potenziell falsch gemacht haben? Also habe ich grad mal ein Testprogramm zusammengeklimpert, um das Problem zu isolieren:

dim psi as New ProcessStartInfo("notepad.exe")
psi.UseShellExecute = false
dim p as new Process()
p.Start(psi)
p.WaitForExit

Auch hier trat der Fehler auf. Gut so, also habe ich ein wenig herumexperimentiert. Bei WaitForExit() habe ich in der Doku einen Hinweis darauf gefunden, dass die Methode fehlschlägt, wenn dem Prozess keine Id oder kein Handle zugewiesen ist. Also habe ich mal Folgendes getestet:

...
p.Start(psi)
console.writeline(p.Id)

Das führte auch zu einem Fehler. Die Spur war also richtig. Aber warum sollte der Prozess, den ich mit new Process() erzeuge, keine Id haben??? Weil die Variable p auf keinen endgültig initialisierten Prozess verweist! Die Anweisungen

dim p as new Process()
p.Start(psi)

erzeugen nämlich zwei Prozessobjekte! Auf eines verweist p, ein anderes wird durch p.Start(psi) generiert, weil die Methode Start(ProcessStartInfo) eine statische Methode der Klasse Process ist. Wer hätte das gedacht? Hätte ich geschrieben

dim p as new Process()
p.StartInfo = psi
p.Start()

wäre alles gut gewesen. Der VS.NET Editor für VB.NET hatte mir jedoch auf p auch die Start()-Methode mit dem ProcessStartInfo-Parameter via IntelliSense gelistet, so dass ich mir das Setzen der Property StartInfo sparen wollte. Ich bin also in eine IntelliSense-Falle getappt. Für VB.NET listet VS.NET auf Instanzvariablen auch statische Methoden ihrer Klasse; der C# Codeeditor tut das nicht. Dort wäre mir das Problem nicht untergekommen.

Korrekt funktioniert es also in jedem Fall wie folgt:

dim p as Process = Process.Start(psi)
p.WaitForExit

Die statische Factory-Methode liefert ein vollständig initialisiertes Prozessobjekt zurück, auf dem dann WaitForExit() aufgerufen werden kann.

So schön VB.NET also ist, hier muss man ein wenig aufpassen. Klassenmethoden auf Instanzen aufrufen zu können, ist vielleicht doch keine so gute Idee. Mir fällt auch grad kein Grund ein, warum es nötig sein sollte. Guter OOP-Stil ist es jedenfalls nicht.

So, jetzt hab ich keine Lust mehr. Ich hab keine Lust mehr, Aufwand zu treiben, um Assemblies im GAC zu installieren: Consolenfenster auf, in das Assembly-Verzeichnis wecheln, gacutil aufrufen, beim Deinstallieren vergessen, dass der Dateiname ohne Extension geschrieben werden muss usw. usf.

Mit dem folgenden Utility ist das Problem einfürallemal gelöst. Und sie leistet noch mehr.

Installation:
Code in ein VB Console App Projekt einsetzen und compilieren. Einmal aufrufen. Das Programm trägt sich in der Registry ein, so dass es per Kontextmenü auf DLL-Dateien aufgerufen werden kann (Eintrag "GacUtils").

Anwendung:
-Kontextmenü im Explorer auf einer Assembly öffnen.
-GacUtils auswählen.
-Aus dem Menü im Konsolenfenster wählen. Das Programm kann Assemblies im GAC registrieren und sie wieder daraus entfernen. Gleichzeitig kann es auch das Verzeichnis der Assembly so registrieren, dass die Assemblies darin in der VS.NET "Add Reference" Dialogbox gelistet werden, wie die .NET Framework Assemblies (s. dazu: http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B306149).

Vielleicht baue ich auch noch die automatische Registrierung in COM+ ein. Mal schauen. Stay tuned.

Viel Spaß erstmal damit.


imports Microsoft.Win32

Module Module1

    Sub Main(args() as string)
        Dim shellKey as RegistryKey = Registry.ClassesRoot.CreateSubKey("dllfile\Shell\GacUtils\Command")
        shellKey.SetValue("", string.Format("{0} ""%1""", Reflection.Assembly.GetExecutingAssembly.Location))

        if args.Length <> 1 then
            msgbox("Usage: GacUtils ", MsgBoxStyle.Information)
            exit sub
        End If

        dim ass as Reflection.Assembly = Reflection.Assembly.LoadFrom(args(0))
        dim assName as String = ass.FullName.Split(",")(0)

        Console.WriteLine("1=Register in GAC, 2=Unreg from GAC, 3=Affect 'Add references' dialog")
        Console.Write("  multiple commands allowed: ")
        dim cmds as String = console.ReadLine
        dim p as Process

        if cmds.IndexOf("2") >= 0 then
            ' delete from GAC
            console.WriteLine("gacutil.exe " & string.Format("/u {0}", assName))
            dim psi as New ProcessStartInfo("gacutil.exe", string.Format("/u {0}", assName))
            psi.UseShellExecute = false
            p = New Process
            p.Start(psi)

            if cmds.IndexOf("3") >= 0 then AffectAddRefListing(assName)
        End If

        if cmds.IndexOf("1") >= 0 then
            ' reg in GAC
            console.WriteLine("gacutil.exe " & string.Format("/i {0}.dll", assName))
            dim psi as New ProcessStartInfo("gacutil.exe", string.Format("/i {0}.dll", assName))
            psi.UseShellExecute = false
            p = new Process
            p.Start(psi)

            if cmds.IndexOf("3") >= 0 then AffectAddRefListing(assName, IO.Path.GetDirectoryName(args(0)))
        End If

        if cmds = "3" then
            AffectAddRefListing(assName, IO.Path.GetDirectoryName(args(0)))
        End If

        console.readline
    End Sub


    private sub AffectAddRefListing(assemblyName as String, optional path as string = "")
        console.WriteLine
        if path <> "" then
            console.WriteLine("Registering directory {0} for inclusion in 'Add references'", path)
            dim refKey as RegistryKey = Registry.LocalMachine.CreateSubKey("Software\Microsoft\.NETFramework\AssemblyFolders\" & assemblyName)
            refKey.SetValue("", path)
        else
            console.WriteLine("Unregistering directory {0} from 'Add references'", path)
            Registry.LocalMachine.DeleteSubKey("Software\Microsoft\.NETFramework\AssemblyFolders\" & assemblyName)
        End If
    End Sub
End Module

Was mich ein wenig Zeit gekostet hat bei der Entwicklung: Wenn man ein Programm über das Kontextmenü startet, dann wir der Name, der Datei, auf die man geklickt hat, als erster Kommandozeilenparameter übergeben. Aber - und das ist der Trick - in seiner Kurzform: alle Namen sind im 8.3 Format. Damit kann gacutil.exe aber nichts anfangen.

Meine Tests hatte ich aus VS.NET mit einem vollständigen Pfad gemacht und alles funktionierte. Sobald ich dann aber über das Kontextmenü gegangen bin, lief es aber schief. Es hat einen Moment gedauert, bis ich darauf gekommen bin. Dafür habe ich dann auch gacutil.exe nicht in einem eigenen Konsolenfenster gestartet (psi.UseShellExecute=false), sondern im Fenster der Utility. Damit konnte ich dann erstmal überhaupt sehen, dass gacutil.exe verschnupft war.

Die Teilnehmer der TechEd wurden heute von ganz speziellen Herren begrüßt:

Im Rahmen seiner Community Initiativen hat Microsoft einige "Software Legends" identifiziert, deren Anwesenheit (auch in Person, nicht nur als Papp-Aufsteller) dem Event einen besonderen Flair verleihen soll. (In der Speaker Lounge war auch unüberhörbar Don Box anwesend :-)

Das ist bestimmt eine gute Idee, Community lebt bei allem gemeinsamem Interesse für eine Sache auch immer von Persönlichkeiten, die durch ihre Art, Meinung, Kompetenz hervortreten. Welche das für welche Community aber sind, ist nicht immer einfach zu bestimmen, würde ich sagen. Wen würde die deutsche Entwicklergemeinde z.B. auf dem folgenden Bild eher als "Software Legend" küren?

Den Herren in der Mitte oder einen der ihn flankierenden Herren? :-) (Um nicht missverstanden zu werden: Ich habe nichts gegen den Herren in der Mitte, Billy Hollis. Im Gegenteil, ich schätz ihn sehr als äußerst kompetenten und auch bekannten Kollegen.)

Don Box ist sicherlich völlig außer Frage eine oder die prototypische Software Legend in der Microsoft Welt. Oder genauer: In der COM-Welt. Denn wie sein Status jetzt ist, da er von Microsoft assimiliert wurde und sich mit XML/SOAP/Web Services beschäftigt, ist unklar, finde ich. Wer also folgt ihm nach? Oder wer ist für welche Zielgruppe eine "Software Legend"? Jeffrey Richter? Billy Hollis? Keith Pleas? David Stutz? Clemens Vasters? Francesco Balena? Tim Ewald? Martin Fowler? Kent Beck? Schwer zu sagen. Bekannte Namen auf einer Konferenz zusammen zu führen (wie es auch andere tun, z.B. SD West in den USA) und damit zu werben ist immer eine gute Idee. Aber sie bewusst mit einem künstlichen, nicht von ihnen selbst gewählten Titel wie "Software Legend" zu belegen finde ich fast schon kontraproduktiv. Es hat etwas von "Microsoft sucht den Superstar" ;-)

Aber genug davon. Es gab auch anderes am für mich letzten Tag auf der TechEd zu sehen. Der vom Montag auf heute verschobene Vortrag von Yasser Shohoud zu Service Oriented Architecture (SOA) und Web Services hat mir z.B. sehr gut gefallen. Das Thema SOA ist einfach sehr wichtig und Yasser hat es sehr pragmatisch demonstriert. Er sprach über die Möglichkeit zur Versionierung von Web Service Schnittstellen, wenn man sie von einem Message-basierten Standpunkt aus betrachtet. Der Aufruf eines Web Service geschieht ja nicht Stack-basiert, sondern über eine SOAP-Nachricht. Deshalb kann in die Signatur einer Web Service Methode ein "Extensibility Point" in Form von Platzhalterparameters eingeplant werden. Ein interessanter Trick, der in Richtung des von mir vor zwei Jahren in einem TechTalk vorgestellten Konzept des "Strong Tagging" geht.

Als letzte Session habe ich mir dann noch einen Chalk Talk gegönnt. Ein Format, das ich noch nicht kannte:

Bei einem Chalk Talk wird ein Thema in kleinem Rahmen (max. 20-30 Teilnehmer) ohne Folien nur mit einem Whiteboard/Flipchart bewaffnet behandelt. Es geht um Spontaneität, Interaktion, direkten Kontakt mit dem Referenten. Das hat mir gut gefallen. Es hat funktioniert - ist aber davon abhängig, dass der Vortragende gleichzeitig technische und moderierende Kompetenz hat.

Besonders schwer war es dann auch noch in diesem Chalk Talk, weil das Thema lautete "Microsoft and Open Source". Leider war ich etwas spät gekommen und hatte verpasst, was zu Mono gesagt wurde. Aber auch die restlichen 45min waren interessant. Der Fokus "Open Source" wurde zwar etwas aus dem Auge verloren, aber Shared Source gehört auch in das Umfeld, ebenfalls Fragen zur Lizenspolitik.

Drei Microsoft-Sprecher und ein externer stellten sich den durchaus emotionalen Anliegen der Teilnehmer. Zu jedem Zeitpunkt war die Stimmung aber trotz kontroverser Ansicht gut. Eine gute Leistung der Referenten. Ihre Antworten waren zu dem sehr breiten Thema so gut es ging in Anbetracht aller "(firmen)politscher Fallen". Viel Selbstkritik war bei Microsoft zu hören, aber auch die Bitte, offensichtliche Verbesserungen anzuerkennen. Als Beispiel mag die Shared Source Initiative dienen. Sie gestattet nahezu jedem interessierten Unternehmen, Sourcecode von Microsoft Produkten einzusehen. 85% des Windows Quellcodes sind zugänglich, große Teile des .NET Framework (in Form der Rotor Implementation) und bald auch Office Produkte. Genutzt wird diese Option jedoch sehr selten. Von 500 befragten Unternehmen in Großbritannien war nur eines interessiert, ein weiteres zeigte Interesse für die Zukunft.

Unabhängig von den konkreten Fragen und Antworten hat mich gerade an diesem Chalk Talk aber beeindruckt, wie offen sich Microsoft einem so kontroversen Thema stellt. Statt von einem Bühne herab, hat man das Interesse (und den "Leidensdruck") der Teilnehmer bei den Hörnern gepackt und sich im persönlich gestellt. Ein schönes Zeichen. Ich halte es für ehrlich gemeint - wenn auch solche Offenheit nur von einem kleineren Teil innerhalb von Microsoft getragen sein mag. Aber vielleicht macht dieses Zeichen ja Mut und Microsoft nimmt Initiativen wie Mono noch bewusster wahr, z.B. in Form von Konferenzen wie der letztjährigen .NET One in Frankfurt.

Nach dem Chalk Talk blieb mir dann nur noch wenig Zeit bis zur Rückreise. Über den weiteren Verlauf der TechEd - insbesondere die Konferenzparty heute abend - kann ich also leider nichts berichten. Schade :-( Andere Verpflichtungen ließen es jedoch nicht zu, noch einen Tag länger zu bleiben. Gern hätte ich Don Box' "Guide to Clarity and Stability" bzw. Web Services gehört oder die Yukon (nächste SQL Server Version) Preview zum Thema Programmierbarkeit oder auch Clemens zu "Aspect Oriented Programming". Der letzte Tag verspricht also nochmal spannend zu werden.

Wenn es aber stimmt, dass alle Sessions mitgeschnitten wurden, dann kann ich die verpassten ja aber vielleicht noch nachholen: Konferenz-DVD einlegen und lauschen...

Einstweilen hat mich aber erstmal München wieder. Das Begrüßungsfarbenspiel auf dem Flugplatz hat mir jedenfalls schon mal gefallen :-)

Schön, wieder daheim zu sein. Morgen geht es dann weiter nach Hamburg für ein paar Wochen. Die Tage dort geben mir hoffentlich Gelegenheit, das Gesehene und Gehörte von der TechEd 2003 sacken zu lassen. Jetzt gilt es, die Inspirationen in konkrete Pläne umzusetzen. InfoPath und SOA stehen oben auf meiner Liste. Dafür hat sich die TechEd allemal gelohnt.

Posted by Ralf Westphal | 2 comment(s)
Filed under:

Auch wenn Partying ein beliebter und auch wichtiger Teil von (so großen) Events wie der TechEd ist, so soll doch nicht der Eindruck entstehen, die Tageszeit wäre nur ein lästiger, irgendwie zu überbrückender Zwischenraum. Nein, nein, die Vorträge sind natürlich wichtig und auch die Hauptsache, wegen der die Teilnehmer auf der TechEd sind.

Ob deshalb die Vorträge so wie sie sind auch gut sind, ist allerdings eine andere Frage. Ich sehe schon eine ganze Menge Verbesserungspotenzial. Da sind z.B. die Grafiken in den PPT-Vorträgen. Sie haben nicht alle die gleiche Qualität oder werden angemessen eingesetzt. Ich würde sagen, mehr Grafiken, mehr Visualisierung können viele Themen vertragen. Wie man es schon mal besser machen kann, hat dem internationalen Publikum unser RD Clemens sehr schön gezeigt. Ich bin immer wieder beeindruckt von seinen Visualisierungen - und frage mich, wann er sich die Zeit nimmt, die ganzen Animationen einzubauen :-)

Bei Ken Getz (meiner persönlichen "Sprecher-Entdeckung" der TechEd 2003) habe ich gestern auch noch vorbei geschaut. Er hat einen hübschen Vortrag zum Thema Datagrid-Control in WebForms gehalten. Das Thema ist ja mit all seinen Optionen nicht ganz einfach und bedarf einiger Erklärungen und Übung. (Meine Leseempfehlung dazu: Building Web Solutions with ASP.NET and ADO.NET, Dino Esposito, MSPress 2002)

Warum hat mir der Vortrag aber gefallen? Gar nicht so sehr wegen seines Inhalts (ich hatte ja schon Dinos Buch gelesen), sondern wegen seiner Form. Wie auch schon bei seinem WinForms-Vortrag hat Ken das Medium Vortrag wirklich genutzt. Beim WinForms-Vortrag hatte er nicht nur Tipps&Tricks gezeigt, sondern auch den Umgang mit VS.NET bzw. das Vorgehen (!) bei der Arbeit. Gleiches hat er dann auch hier getan. Tipps&Tricks kann man durch Lesen lernen, den Umgang (!), ein Vorgehen, d.h. einen Prozess jedoch besser durch eine Demonstration. Da wo Attribute wie "Leichtigkeit" und "Geschwindigkeit" oder ein Vorgehen vermittelt werden sollen, bietet sich ein Vortrag statt Lektüre an. (Dazu gehören dann womöglich auch (bewusste) Fehlentscheidungen während einer Demonstration, die dann aber auch live ausgebügelt werden.)

Für Events bedeutet das: Um die immer knapper werdende Zeit aller Teilnehmer möglichst effektiv zu nutzen, sollte bei jedem Vortrag überlegt werden, ob, was und wieviel Mehrwert er gegenüber z.B. einem technischen Artikel zum selben Thema er hat. Keine leichte Aufgabe, aber eine die lohnt, angegangen zu werden. Für meine eigenen Vorträge habe ich schon mal versucht, etwas zu verändern. Weniger Slides, mehr Code ist mein aktuelles "Experiment". Denn Text auf Slides kann man auch allein im Kämmerlein lesen; Code zwar auch - aber den Prozess der Codeentwicklung, die Benutzung der Tools dabei, den Entscheidungsfluss, den kann man als Autor viel schwerer in einen Text fassen (auch wenn es bei genügend Platz nicht unmöglich ist). Über "no slides, just code" hinaus kann man aber auch noch anderes tun, würde ich sagen. Und das, was ich auf der TechEd gesehen habe, ist mir ansporn, zukünftig mehr auszuprobieren, um das Medium Vortrag noch besser zu nutzen.

Neben den Vorträgen gibt es auch eine Ausstellung mit Ständen von Borland, Intel, WISE, Microsoft und vielen anderen größeren und kleineren Anbietern. Leider habe ich keine Zeit gefunden, mich zwischen Vorträgen, Bloggen und Parties in diesem Bereich zu tummeln. Schade eigentlich, denn den C# Builder von Borland als alternative .NET Framework Entwicklungsumgebung hätte ich mir schon gern näher angesehen oder auch InfoPath - mein persönliches Highlight der TechEd - oder BizTalk 2004 oder ein SmartPhone oder andere Dinge mehr. Alles erklärungsbedürftige Technologien, die man halt nicht in 5min zwischen Tür und Angel kennenlernen kann und für die eine persönliche Demo ein geeigneter Einstieg ist (wenn das Standpersonal in der Lage ist, auf gezielte Fragen einzugehen). (Trotz Demosoftware, Video und Internet machen auch Messen immer noch Sinn.)

Beim Passieren des Ausstellungsbereiches auf dem Weg zu einem Vortrag bin ich dann aber doch über einen Stand gestolpert, der sich sehr unerwartet präsentiert hat:

Was hat ein Pinguin auf einer Microsoft-Veranstaltung zu suchen? Und dann auch noch einer, der die Muskeln spielen lässt! Bei näherem Hinsehen war dann zwar schon ein Fragezeichen auf dem Plakat zu erkennen, aber so ganz klar ist mir die Grafik dann doch nicht geworden. Kein Standpersonal, nur ein Flyer hat versucht, Abhilfe zu schaffen.

Es ging um ACT (http://www.actonline.org), eine Gesellschaft zur Föderung der Vielfalt und Freiheit der Softwareentwicklung, die Lobbyarbeit in Washington DC leistet. Dass das Plakat wahrscheinlich nur ein unglücklicher Versuch war, auf drohende Gefahren für die Softwarebranche im Allgemeinen und als Ganzes (!) aufmerksam zu machen, wird durch den Präsidenten von ACT deutlich: Jonathan Zuck ist einer der Microsoft Regional Directors und sicherlich niemand, der versucht, auf der TechEd Linux "einzuschmuggeln".

Ob ACT eine gute Arbeit macht, kann ich nicht sagen. Aber die Existenz von ACT hat mir bewusst gemacht, dass die Softwarebranche offensichtlich groß genug ist, um eine eigene Interessenvertretung zu haben. Andere Branchen und Professionen haben eine lange Verbandstradition auf Arbeitgeber und -nehmerseite. Seien es der Bund deutscher Unternehmer oder der Deutsche Journalisten Verband oder die Gewerkschaften. Für die Softwarebranche mit ihren neuen/anderen Rechteverhältnissen und so ganz anderen Dynamik scheint es mir aber kein Äquivalent zu geben. Wo sind Microsoft, SAP, Borland, sd&m und die vielen kleinen Softwareschmieden organisiert, um ihre Interessen gegenüber dem Gesetzgeber zu vertreten? Und wo sind freiberufliche wie auch (noch) angestellte Softwareentwickler zusammengeschlossen, um ihre Interessen gegenüber Auftraggebern und Arbeitgebern zu vertreten? Es gäbe viel zu tun: Werk-/Projektverträge, Rechtsbeistand, Weiterbildung, Ausbildungsstandards, Berufsbilder, "Code of Conduct" und mehr.

Posted by Ralf Westphal | with no comments
Filed under:

Technischen Inhalten gehört auf der TechEd der Tag.
Der Nacht gehören Geselligkeit, Community, das persönliche Gespräch, der Spaß. Und das ist gut so. Irgendwann muss man ja auch mal die Informationsflut sacken lassen :-)

Die TechEd Organisation hat es daher sehr schön eingerichtet, dass quasi allabendlich einer oder mehrere Events stattfinden. Der Dienstag stand dabei im Zeichen der "Country Drink Receptions": Die Microsoft Geschäftsstellen von Deutschland, Schweiz, Norwegen usw. usf. laden dann zu einem get together "ihrer Schäfchen" an einen interessanten Ort ein.

Microsoft Deutschland hatte eine der bekannten Diskotheken in Barcelona ausgesucht.

Das Ambiente war zwar sehr dunkel, gerade wenn man von der sonnendurchfluteten Straße eintrat - aber kühl war die Atmosphäre deshalb noch lange nicht.

Nach einer Begrüßung durch Sven Dummer von Microsoft Deutschland

gab Drinks, leckere Häppchen und Musik. Licht&Musik Jockeys haben für eine gute Untermahlung für Tanz, Unterhaltung und Diskussion gesorgt:

Aber auch andere Länder hatten sich etwas einfallen lassen: die Schweizer haben sich in ein Kloster zurückgezogen (Montserrat), die Norweger in das Hard Rock Café:


Posted by Ralf Westphal | with no comments
Filed under:

Wer feiern kann, kann auch arbeiten! So heißt es zurecht und so begann der zweite Tag der TechEd schon um 8:30h mit Vorträgen. Eingeladen zu einem Roundtable Gespräch um 10:15h mit dem Keynoter Sanjay Parthasarathy vom ersten Tag, habe ich den Weg zum Gelände erst gegen 9:30h gefunden und dann die Zeit genutzt, um etwas Email aufzuarbeiten.

Derweil gab es ein kurzes Briefing für die Teilnehmer,

um Sie auf die Gelegenheit vorzubereiten, mit einem Microsoft VP über ihre Community Angelegenheiten zu sprechen. Eingeladen waren Mitglieder aller Microsoft Community Programme (s. http://www.microsoft.com/germany/ms/community/verzeichnis/msdn_entwickler.htm für eine Übersicht): Microsoft MSDN Regional Directors, MVP, CodeWise usw.

Während des knapp 60minütigen Termins war interessant zu sehen, dass Sanjay und andere Microsoft Community Verantwortliche ernsthaft versucht haben, in der Kürze der Zeit ein Feedback zu den Microsoft Community Initiativen zu bekommen. So verschieden die "brennenden Fragen" der Teilnehmer auch waren, am Ende kam eine Liste von drei "Hausaufgabenpunkten" für Microsoft zusammen, für die erste Ergebnisse in den nächsten 3 Monaten angestrebt sind. Auf dem Zettel stehen vor allem eine größere Transparenz der Community Programme ("Welches Programm steht eigentlich für was?") und die Erarbeitung eines Systems für Community Leader, um sie auch bei steigenden Lasten in ihrem "eigentlichen Job" zu motivieren, sich in ihren Communities zu engagieren. Wir werden sehen, ob und wie Microsoft seine Hausaufgaben macht. Ich bin gespannt und optimistisch, dass Microsoft es sehr ernst meint mit seinem Community Engagement.

Nach dem Roundtablegespräch blieb dann nicht mehr allzuviel Zeit für anregende technische Inhalte. Das Mittagessen drohte und damit ein sehr interessanter Businesslunch. Wenn die Ideen, die dabei ausgetauscht wurden, wirklich umgesetzt werden, dann tut sich ein ganz neues Medium für die Vermittlung technischer Inhalte auf. Mal schauen... :-)

Leider kann ich von den Nachmittagsvorträgen, die ich besucht habe, nichts gutes berichten :-( Das heißt nicht, sie wären per se schlecht gewesen, sondern sie haben mir persönlich nichts gegeben.

Zwei grundsätzliche Kritikpunkte kann ich trotzdem aber nennen:

Zwar sind auf dem Veranstaltungsgelände mehrere Wireless LAN Bereiche eingerichtet - aber die Arbeit mit dem WLAN ist sehr mühsam. Auch wenn die Signalstärke excellent ist, ist der Internetzugriff meist sehr, sehr langsam. Auch während der Sessions, wenn nicht viele Teilnehmer in den Bereichen sind. Woran liegts? Wahrscheinlich daran, dass zuviele Access Points zu dicht beieinander stehen. Ein Teilnehmer will sogar 24 (!) verschiedene Netze an einem Punkt "gemessen" haben. Dass dabei Kollisionen im Datenverkehr vorprogrammiert sind, ist klar. Microsoft hat es zwar gut gemeint mit soviele Access Points - aber nicht gilt halt, dass viel auch viel hilft.

Wie auf allen internationalen Microsoft-Veranstaltungen sind auch hier die Sessions wieder mit Levels gekennzeichnet. Gerade war ich in einem 400er Vortrag, also dem höchsten Level. Der Inhalt aber war kaum eine 200er Bewertung wert. Sehr enttäuschend. Unabhängig davon, ob der Inhalt den Versprechungen aus dem Abstract bzw. der Überschrift entsprochen hat (er hat es nicht, meiner Meinung nach, denn unter "Service Oriented Architecture" verstehe ich mehr als eine Web Service Demo), verspricht sein vermeintliches Niveau eine ganze Menge oder hält auf der anderen Seite möglicherweise interessierte Teilnehmer "aus Angst" von einem Besuch ab. Microsoft sollte am besten einfürallemal auf die Levelzuordnung verzichten. Denn letztlich ist jeder Vortrag für irgendjemanden immer 400 oder 100. Wichtiger als eine Levelzuordnung wäre eine klarere Beschreibung der Voraussetzungen für jeden Talk. Dann kann jeder Teilnehmer beurteilen, ob er sie hat oder nicht (also den Schwierigkeitsgrad der Session für sich bestimmen).

Ach, und wo ich grad dabei bin: Auch Overlap sollte noch besser vermieden werden. Der InfoPath Vortrag heute Vormittag, auf den ich mich sehr gefreut hatte, war sterbenslangweilig, weil er im Prinzip das etwas langsamer wiederholt hat, was gestern schon gezeigt worden war. (Und das, obwohl der Referent am Anfang gebeten hatte, man möge aufzeigen, ob man gestern da gewesen war - und viele Hände sind hoch gegangen.)

Genug aber nun des "Gejammers". Jetzt noch ein paar Minuten in einen der letzten Vorträge für heute und dann zur Country Reception. It´s party time again! Microsoft Deutschland lädt ein.

Posted by Ralf Westphal | 1 comment(s)
Filed under:
More Posts Next page »