Montag, 28. April 2008

S# oder wie GENIAL ist das denn?

Absoluter Zufall das ich darauf gestoßen bin... das mußte ich gleich in unserem Blog verewigen

Script# oder S# ist ein C# nach JavaScript "Konverter", die Idee is einfach genial umgesetzt 8-)
Man entwickelt in C# gegen ein Interface und bekommt am Ende JavaScript...

Das Projekt kann man von Codeplex beziehen: http://www.codeplex.com/scriptsharp

Eine kurze Einführung gibt es vom Entwickler selbst hier:
http://www.nikhilk.net/ScriptSharpIntro.aspx


Update 25.08.2008: Das Projekt ist nun unter http://www.codeplex.com/scriptdotnet zu erreichen.

Freitag, 25. April 2008

Links SSH und SFTP in C#

Wie im vorherigen schon erwähnt beschäftige ich mich gerade mit SFTP und SSH. Leider gibt es nicht viel an Vorlagen in C#, darum trage ich mal alles zusammen, was ich an interessantem gefunden habe.

Erstmal auf ein Phänomen das ich oft treffe, SFTP und FTPS werden brachial durcheinandergeschmissen. Daher kurz:
SFTP - ein Paketbasiertes Protokoll das auf SSH aufsetzt
FTPS - ein Komandobasiertes Protokoll das auf FTP aufsetzt und einen SSL verschlüsselten Kanal verwendet

Weitere Infos gibt es übersichtlich hier: http://winscp.net/eng/docs/protocols#protocol_comparison
und hier: http://searchsecurity.techtarget.com/expert/KnowledgebaseAnswer/0,289625,sid14_gci1232457,00.html

Daraus ergeben sich 2 Umstände:
1. FTPS ist schneller als SFTP
2. Die FTP Beispiel Implementierung von Mircosoft funktioniert nicht für SFTP (es ist ein anderes Basis Protokoll!)

FTP sendet Daten (auch Passwörter) unverschlüsselt durchs Web zum Server, darum kann man mit FTPS eine SSL Verbindung etablieren, die diesen Verkehr verschlüsselt. Unter Unix is SSH etabliert und hier muss ich auf einen SFTP Server zugreifen. Doch dummerweise ist gerade SSH in .NET nicht implementiert von Microsoft.

Schnell will ich die Liste der kommerziellen Produkte abhaken, dazu hab ich zwei interessante Links gefunden:
- http://www.trilead.com/Products/Trilead-SSH/ (scheint aus JAVAs Ganymed hervorgegangen zu sein)
- http://www.eldos.com/sbb/sftpcompare.php (wobei dieser Vergleich mit etwas Vorsicht zu geniessen ist da schließlich Eldos sein Produkt besonders pushen möchte, dennoch ein erster Überblick und Startpunkt)

Nun was gibt es auf der Open Source Seite? Da sieht es ein wenig arg düster aus irgendwie:
- Granados - Der japanische Hersteller Routrek bietet eine Open Source Implementierung von SSH unter der liberalen Apache License zur Verfügung: http://www.routrek.co.jp/en/product/varaterm/granados.html
- SharpSsh - Der israelische Entwickler Tamir Gal hat einen Port von Jsch programmiert und stellt dies unter der ebenfalls liberalen BSD License zur Verfügung: http://www.tamirgal.com/home/dev.aspx?Item=SharpSsh
Dazu hat er noch einen Beitrag auf Codeproject verfasst.
- SharpSsh bedient sich dabei der GPL freien Kryptographie Libraries von Mentalis: http://www.mentalis.org/soft/projects/crypto/

Eine Recherche via Googles Codesearch endete darin, dass alle von mir gefundenen Implementierungen auf Tamir Gals Port aufsetzen.

SSH Testserver aufsetzen

Ich muss gerade einen SFTP/SCP Client coden und da mir der Kunde keinen Server zum testen für den Anfang zur Verfügung stellt, brauch ich einen auf die schnelle in meiner Entwicklungs VM.

Nach ein wenig googeln findet man sshwindows eine OpenSSH Umsetzung, diese Implementierung setzt auf den UNIX Emulator cygwin in abgespeckter Form auf (wer gcc verwendet dürfte den kennen) und hat vollen SSH(1+2)/SCP/SFTP Support. Der letzte Stand ist vom Juli/2004 und wird leider nicht mehr gewartet seit dem. Wer etwas mehr up-to-date benötigt kann sich auf OpenSSH nach einer aktuelleren Windows Implementierung umsehen.

sshwindows ist simpel in Installation und Konfiguration und reicht für meine einfachen Testzwecke aus (es dauert länger diesen Blogeintrag zu schreiben, als es zu konfigurieren...).

Die Installation ist straight forward, danach hat man im Startmenu (respektiver docs Unterverzeichnis) einen Quick Start Guide.

Hier nochmal kurz, für einen lokalen User (domain user ist fast identisch, siehe Quick Start Guide)
- Command Prompt öffnen
- In das bin Verzeichnis der OpenSSH Installation wechseln
- eingeben: "mkgroup -l >> ..\etc\group"
- eingeben (Beispiel Administrator User): "mkpasswd -l -u Administrator >> ..\etc\passwd"
- Server starten mit: "net start opensshd"

Für das starten hab ich mir eine kleine Batch Datei geschrieben:

   1:  :: change to the OpenSSH bin dir
   2:  @ECHO OFF
   3:   
   4:  CD C:\OpenSSH\bin
   5:   
   6:  ECHO.
   7:   
   8:  :: start SFTP
   9:  net start opensshd
  10:   
  11:  :END

Jetzt ist der lokale Administrator User als Login eingerichtet mit den Windows Credentials für unseren Test SFTP Server. Anmerkung: Um ihn nun zu verwenden mußte ich den Rechner erst neu starten, ansonsten scheiterte der Login mit einem Error!

Als Testclient hab ich FileZilla verwendet. Nach der Installation, einfach:
Host: localhost
User: Administrator
Password: <windows password>
Port: 22
eingeben und Quickconnect.

Jetzt liegt das FTP Verzeichnis noch etwas sehr hässlich im Home Verzeichnis des Administrators (also z.B. je nach verwendetem Windows: C:\Documents and Settings\Administrator\).

Das kann man auch sehr schnell und einfach anpassen:
Im Verzeichnis \etc liegt die Datei passwd. Im Texteditior findet man ziemlich nahe am Schluss den Eintrag: /home/Administrator den habe ich ersetzt durch z.B.: /cygdrive/c/FTPDir (cygdrive/c/ ist die Deklaration das Windows C:\ zu verwenden)

Dienstag, 22. April 2008

I've joined the Dark Side!

Ja, in der Tat, ich bin heute auf die Dunkle Seite gewechselt, bin jetzt ein Dunkler Entwickler! Aber langsam, von was rede ich da eigentlich? Dunkle Seite von was? Die Dunkle Seite von Visual Studio natürlich!

Wer jetzt keine Ahnung hat, wovon ich rede, hat in letzter Zeit die diversen Blogs bekannter .Net-Entwickler nicht aufmerksam gelesen. Ab in die Ecke dafür! Aber ich will mal nicht so sein und euch verraten, um was es geht. Vor nicht all zu langer Zeit ging es durch die Weiten des Webs, einige Entwickler, die tagtäglich mit Visual Studio arbeiten, wollten sich nicht mehr mit der Standard-Oberfläche abgeben und taten etwas dagegen. Beteiligt waren daran mit Sicherheit Entwickler, die des öfteren in Stunden programmieren, in denen die natürliche Beleuchtung ausgefallen ist und die Ersatzbeleuchtung den Augen viel zu grell. Richtig, ich rede von der Nacht. Das Weiß des standardmäßigen Hintergrunds von Visual Studio hat in eben jenen Stunden den großen Nachteil, sehr grell zu sein und die Augen über kurz oder lang zu ermüden. Daher bestand die Lösung der findigen Entwickler darin, eine Konfiguration für Visual Studio zu erstellen, die die Augen weniger fordert, die Dark Side of Visual Studio war geboren.

Wenn man sich diesen Blog (zumindest im aktuellen Layout, wer weiß, was die Zukunft bringt...) anschaut, fällt einem auf, dass auch hier alles ziemlich dunkel gehalten ist. Da lag es also nahe, dass auch ich mich der Dunklen Seite verschrieb. Die erste Anlaufstelle dafür ist die Website die auffordert: JOIN THE DARK SIDE OF VISUAL STUDIO. Genau hier gibt es die Settings herunter zu laden, die einen auf die dunkle Seite der Programmierung ziehen wollen - allerdings nur für Visual Studio 2008. Die entsprechenden Settings für Visual Studio 2003 (VORSICHT: hier wird ein spezielles Styler-AddIn benötigt!) und 2005 gibt es in diesem Blog zum Download.

Da wahrscheinlich nicht jedem sofort klar ist, wie man diese Settings jetzt in sein Visual Studio bekommt, folgt jetzt eine kleine Anleitung.

Zuerst mal wird das herunter geladene ZIP-File entpackt. Anschließend öffnet man das Studio. Hier geht es weiter mit einem Klick auf Extras --> Einstellungen importieren und exportieren...

1

Im darauf folgenden Assistenten wird der RadioButton vor Ausgewählte Umgebungseinstellungen importieren aktiviert.

2

Ein Klick auf Weiter und es präsentiert sich eine Auswahl, die vorschlägt, die aktuellen Settings vor dem Laden der neuen zu sichern. Das halte ich persönlich für absolut sinnvoll, denn was ist, wenn man nicht für die Dunkle Seite gemacht ist? Ok, es gibt eine Option zum Zurücksetzen der Settings, aber dann ist alles wieder wirklich Standard. An dieser Stelle sollte jeder selbst entscheiden, wie er verfahren möchte. Wie auch immer die Entscheidung ausgefallen ist, nach einem Klick auf Weiter erscheint ein Fenster, in dem einige bereits bekannte Settings zum Import zur Auswahl stehen. Die von uns gewünschten sind hier jedoch nicht vorhanden. Da hilft ein Klick auf den Button Durchsuchen. Hat man dann das Setting gefunden, das man verwenden will, geht es Weiter.

3

Es erscheint nochmal eine Abfrage, die zur Wahl der zu importierenden Einstellungen auffordert. Hier kann man einfach auf Fertig stellen klicken.

4

Anschließend denkt Visual Studio ein wenig darüber nach, was ihm da jetzt angetan wurde, fügt sich jedoch schlussendlich doch folgsam in sein Schicksal und meldet dies auch netterweise mit der Meldung Importvorgang abgeschlossen. Braves kleines Ding.

Wird jetzt ein Projekt geöffnet, müsste das ganze in etwa so aussehen:

5

Welcome to the Dark Side!

Mir persönlich ist die Schrift um Längen zu groß, doch das stellt zum Glück kein großes Problem dar. Will man die Schriftgröße verändern, navigiert man zu Extras --> Optionen..., woraufhin sich der Optionen-Dialog öffnet. Hier wird der Punkt Umgebung erweitert und auf den Unterpunkt Schriftarten und Farben geklickt. Auf der rechten Seite kann jetzt die Schriftgröße reguliert werden, welche nach einem Klick auf OK auch prompt angewendet wird.

Damit sind auch schon alle Arbeiten abgeschlossen. Das Endergebnis kann sich sehen lassen, wie ich denke. Eventuell fällt jetzt etwas Eingewöhnungszeit an, schließlich sind die meisten von uns die Standard-Settings doch sehr gewohnt. Ich selbst habe mich erst heute der Dunklen Seite verschrieben, weshalb ich noch keine wirklichen Erfahrungen damit gemacht habe. Jedoch denke ich, dass mein Studio den Rest seiner Tage auf der Dunklen Seite verbringen wird. Eventuell konnte ich ja den ein oder anderen animieren, sich auch mal auf die Dunkle Seite zu wagen, die Gefahren sind dank Sicherung der vorherigen Settings ja durchaus gering. Falls auch der werte Leser sich in das Dunkel gewagt hat, wünsche ich ihm das, was auch die offizielle Seite des "Projekts" wünscht:

May the dark side of Visual Studio be with you!

Noch ein kleiner Nachtrag: Das hier gezeigte Setting eignet sich auch hervorragend für eine Präsentation, wie ich live miterleben durfte.

URI - Scheme erkennen?

Wie finde ich am einfachsten das URI Schema in einem String mit einer vermeidlichen URI?

Nun mein erster Gedanke ist ich komme wohl nicht herum mit einer Liste gegenzuprüfen

Der Ansatz: suche nach ":" Doppelpunkt, setzt voraus das man auch tatsächlich eine vollständige URI bekommen hat. Dies schlägt leider fehl, sobald man einen Host mit einem Port bekommt alla "localhost:80". Unweigerlich wird man wie im C# eigenem Uri Datentyp "localhost" als Schema interpretiert bekommen. Eine Erkennung ob da ein "http://" vorne fehlt, fällt aus.

Bliebe also der Ansatz, ich vergleiche das erhaltene Schema mit bekannten Schemas. Was auch nicht wirklich ideal ist.

Nun, zugegeben die Frage ufert, praktisch gesehen, unnötig aus. Meine Problemstellung war das ich aus einem System einmal Kunden Webadressen mit oder ohne "http://" bekommen kann, und dies entsprechend beim schreiben nach CRM korrigieren soll. Doch halt! Da war noch was? Genau "https://", ergo ergab sich schnell die Frage ob ich es allgemein abfragen kann...

Doch das funktioniert leider nicht da URI nur als Zeichenkette aus Buchstaben, Zahlen, -, +, . definiert ist vor einem : . Damit ist eine Verwechslung nicht ausgeschlossen mit einem Hostnamen dem eine Portdeklaration folgt!

Mein aktueller Ansatz für mein ganz eigenes spezielles Problem geht in Richtung if(!stringX.Contains(@"://")) ... damit lasse ich ganz bewußt URI Schemas wie file: oder mailto: ausfallen, da die in meinem Fall maximal unwahrscheinlich sind als Kundenadressen, es aber sehr viel wahrscheinlicher ist das ich eine unvollständige Adresse im Stile xyz.de:5000 bekomme.

Gibt es eine schönere, allgemeinere Lösung?

WPF & XAML MSDN-Webcast

Hier ein sehr schöner Link zu einem MSDN-Webcast zum Thema XAML und WPF (Windows Presentation Foundation).

Sehr schön, um sich einen schnellen Überblick über die Materie zu verschaffen.

MSDN-Webcast zu XAML und WPF

(thx to Tropensturm, ohne Ihn hätte ich das nicht gefunden ;) )

string.Compare versus ==

leftString.ToLower() == rightString.ToLower()

vs

int string.Compare(string leftString, string rightString, bool ignoreCase)


Zusammenfassung:
- ToUpper()/ToLower() ist 3x langsamer als string.Compare, durch das Erzeugen und Entfernen von temporäreren Strings
- string.Compare prüft auch gleich noch die "Rangordnung": == 0 wenn identisch, == 1 wenn links "größer" als rechts, == -1 wenn links "kleiner" als rechts
- string.Compare kann man eine "Cultural Information" mitgeben (Sortierungsrelevant, falls in einem anderen "Kulturkreis", in dem man vor hat zu deployen, Zeichen anders sortiert werden sollte, man aber seine "eigene" Reihenfolge erwartet, oder auch eben nicht)

Mal abgesehen von der Effizenz Steigerung, string.Compare hab ich mir persönlich aus einem ganz anderen Grund ausgesucht neben Performance: string.Compare ist es egal wenn einer oder beide strings "null" ist, ToUpper()/ToLower(), die eine konkrete Instanz benötigen, nicht (NullReferenceException)
Sprich, man spart sich auch noch gleich ein abprüfen der beiden Parameter, Effizenz in allen Bereichen, Performance, Speicher und Tippen ... wunderbar ;)

Montag, 21. April 2008

CRM 4.0 Prefix für neue Attribute

Kann man im Feld "prefix" unter Settings -> Administration -> System Settings -> Customization einstellen:

Freitag, 18. April 2008

CRM 4.0 Implementation Guide 4.1.0

http://www.microsoft.com/downloads/details.aspx?FamilyID=1ceb5e01-de9f-48c0-8ce2-51633ebf4714&DisplayLang=en

Neben mehreren Korrekturen gibt es 3 neue Kapitel:

  • sichere C/S Kommunikation mit dem CRM
  • Key Management
  • sowie Netzwerkports die vom CRM genutzt werden

Quelle:
http://blogs.msdn.com/crm/archive/2008/04/17/microsoft-dynamics-crm-4-0-implementation-guide-update-4-1-0-is-now-available.aspx

Donnerstag, 17. April 2008

Speichern von UserSettings

Das Speichern von Konfigurationseinstellungen ist sicherlich für viele von uns etwas, das einem ständig über den Weg läuft. Leider war es unter der Version 1.1 des .Net-Frameworks nicht möglich, in die Konfigurationsdatei zu schreiben, was bedeutete, dass man eine eigene Lösung, meist basierend auf XML, erstellen musste. Seit der Version 2.0 ist es dem Programmierer jedoch erlaubt, in eben jene Datei zu schreiben. Geradezu paradiesisch, wenn man das nicht gewohnt ist.

Wie genau das zu bewerkstelligen ist, kann man bei MyMSDN nachlesen, und zwar genau hier: Wie speichere ich Konfigurationseinstellungen meiner Anwender?

An und für sich ist die Anleitung dort sehr gut, doch mir persönlich fehlen einige Informationen. Beispielsweise wird nicht darauf eingegangen, wie man auf einfache Art und Weise die Konfigurationsdatei anpassen kann. Aus diesem Grund werde ich das Vorgehen hier noch einmal kurz erläutern, um auch die letzten Fragen aus dem Weg zu räumen.

  1. Erstellen der Settings
    Wurde mit Hilfe von Visual Studio eine Solution angelegt, so findet sich zu Beginn die folgende Ordnerstruktur:

    1

    Erweitert man hier den Ordner Properties, findet man eine Datei Namens Settings.settings. Öffnet man diese, hat man die folgende Übersicht vor sich:

    2

    In diesem Fenster ist es nun möglich, die benötigten Konfigurationsschlüssel, deren Datentypen sowie die Default-Werte einzutragen. Um den Wert des Konfigurationsschlüssels ändern zu können, muss im Feld Bereich unbedingt Benutzer eingetragen sein. Wird statt dessen Anwendung verwendet, ist der Konfigurationsschlüssel aus dem Code heraus nicht veränderbar. Diese sollten für Werte verwendet werden, die immer gleich bleiben, beispielsweise die Adresse eines Webservice.
    Ist die Bearbeitung abgeschlossen, kann die Datei gespeichert und geschlossen werden.

    HINWEIS:
    Wirt man jetzt einen Blick in die Datei app.config, wird man feststellen, dass die soeben in der Datei Settings.settings erstellten Konfigurationsschlüssel hier eingetragen wurden. Das bedeutet, dass man hier auch direkt das XML hätte bearbeiten können, doch ist die Erstellung über die Datei wesentlich einfacher, weshalb diese sicherlich bevorzugt werden wird.

  2. Lesen von Konfigurationsdaten
    Der erste Schritt, um via Code die Konfiguration auszulesen bzw. zu schreiben, ist ein Using-Statement. Es muss der Namespace der eigenen Applikation, gefolgt von einem Properties eingebunden werden. In unserem Beispiel wäre das also:

       1:  using WindowsFormsApplication1.Properties;

    Nun kann eine Instanz der Klasse Settings angelegt werden, die unsere Konfigurationsschlüssel in Form von Properties vorhält. Dem entsprechend einfach ist es dann auch, die Werte auszulesen. Die Instanz sollte sinnvollerweise als privates Feld angelegt werden, damit der Zugriff darauf jederzeit möglich ist. Der ideale Ort zum Auslesen der Konfiguration ist das FormLoad-Event.

       1:  Settings userSettings = new Settings();
       2:   
       3:  private void Form1_Load(object sender, EventArgs e)
       4:  {
       5:      this.textBox1.Text = userSettings.Text;
       6:  }

    Hiermit wird der Schlüssel Text aus der Konfiguration ausgelesen und in die Textbox geschrieben. Wird die Applikation nun gestartet, sehen wir den Erfolg:

    3

    Der in unserer Konfiguration hinterlegte Text wurde tatsächlich in die TextBox geschrieben.

  3. Schreiben von Konfigurationsdaten
    Entsprechend des Vorgehens zum Lesen der Konfiguration funktioniert auch das Schreiben. Wir weisen einfach der zu ändernden Eigenschaft einen neuen Wert zu. Was man an dieser Stelle jedoch unbedingt beachten muss, damit der Wert tatsächlich gespeichert wird, ist der Aufruf der Methode Save der Settings-Instanz.
    Der beste Ort zum Speichern der veränderten Werte, zumindest in unserem Beispiel, ist das FormClosed-Event. In der Regel bietet sich hier allerdings eher das Click-Event eines Speichern-Buttons an. Der Code für unser Beispiel gestaltet sich wie folgt:

       1:  private void Form1_FormClosed(object sender, FormClosedEventArgs e)
       2:  {
       3:      userSettings.Text = this.textBox1.Text;
       4:      userSettings.Save();
       5:  }

    Wenn wir jetzt die Applikation erneut starten, den Text ändern, die Applikation beenden und gleich darauf wieder starten, sollte der geänderte Text in der TextBox sichtbar sein. Und siehe da, es hat - jedenfalls bei mir - wunderbar funktioniert.

    4

  4. Wichtiger Hinweis zum Schluss
    Etwas ganz Wichtiges noch zum Abschluss dieses kleinen Exkurses.
    Schaut man in die *.conifg-Datei, die im Verzeichnis liegt, in das die kompilierte Datei von Visual Studio gelegt wird, stellt man fest, dass hier nach wie vor der Standard-Wert steht. Startet man jedoch die Applikation, ist nicht etwa der Default-Wert zu sehen, sondern der geänderte. Um diesem Rätsel auf die Spur zu gehen, sollte man den Ordner C:\Dokumente und Einstellungen\Windows_Benutzer\Lokale Einstellungen\Anwendungsdaten\ aufsuchen. Dort findet sich ein Ordner, der den Namen der in der AssemblyInfo eingetragenen Firmennamen trägt, darunter einen mit dem Namen der Applikation und hierunter wiederum einen mit der entsprechenden Version. Ist man dort endlich angekommen, findet man eine Datei namens user.config. Schaut man sich nun diese Datei an, wird man den Wert finden, der auch tatsächlich in unserem Test innerhalb der TextBox zu sehen ist. Hier werden also unsere Konfigurationswerte abgelegt. Die *.config-Datei dient demnach nur noch dazu, um zum einen auf die user.config zu verweisen und zum anderen, um die Default-Werte vorzuhalten, die jederzeit mit dem Aufruf

       1:  userSettings = Settings.Default;

    wiederhergestellt werden können.
    Will man seine Applikation wieder restlos aus dem System tilgen, müssen also nicht nur *.exe und *.config vom Ausführungsort entfernt werden, sondern auch die user.config aus dem Anwendungsdaten-Ordner.

  5. Fazit
    Wie hier zu sehen ist, ist mit dem .Net-Framework in der Version 2.0 das Schreiben von Konfigurationsdaten extrem simpel geworden. Einfach grandios, wenn man an die Verrenkungen denkt, die man teilweise unter der Framework-Version 1.1 machen musste, um seine Konfiguration "benutzbar" zu machen.
    Ich hoffe, ich konnte einigen etwas neues zeigen und ihnen so das Leben erleichtern.

Mittwoch, 16. April 2008

Nachschlagewerk für .NET

Ein interessantes Projekt gibt es hier http://www.dotnet-blogbook.com/ das Blogbook wird regelmäßig aktualisiert mit festen Releaseterminen in frei zugänglicher PDF Form. Es ist eine Art Mix aus Einsteigerwerk, Codeschnipselsammlung und Kochbuch. Also irgendwie ähnlich zu unserem Blogprojekt nur das es mehr auf Grundlagen eingeht 8)

Man muss sicherlich nicht mit jeder Wertung der Autoren einverstanden sein, z.B. wer schon zu faul ist ein IClonable als Shallow oder Deep Implementierung zu dokumentieren, wird sich wohl kaum dazu aufraffen eigene, aussagekräftigere Interfaces zu definieren. Meiner Meinung nach etwas zu idealistisch gedacht. Faulheit von Entwicklern sowie Nullakzeptanz von Geldgebern gegenüber Dokumentationsaufwänden kann man so nicht erschlagen. Mal abgesehen davon das es eine nicht unerhebliche Menge an Entwicklern gibt die davon noch nie gehört haben meiner Erfahrung nach und wohl eher dazu tendieren ein gedankenloses copy and paste durchzuführen. Allein schon aus diesen Fakten resultieren kann man davon ausgehen das es sich zumeist um ein Shallow Copy handelt, wer sich den Aufwand um ein Deep Copy macht wird auch eher sich dazu hinreissen lassen es in der Doku zumindestens als solches zu kennzeichnen.
Ein weiterer Kritikpunkt, der mir persönlich etwas sauer aufstößt, ist dass ab und an eine Prosa Luftblase eine Erklärungen als Antwort auf das Warum ausspart. das ein paar Grundlagenerklärungen fehlen um sich selbst das "Warum" zu beantworten (siehe dazu Kommentare und meine Ausführungen zu Anonyme Typen).

Das (Blog)Buch ist absolut interessant und so ein Projekt immer würdigenswert! Die festen Releasetermine machen Hoffnung auf mehr. Ich finde es gut geeignet für Quereinsteiger und Anfänger mit Grundkenntnissen. Auch für Fortgeschrittene ist so einiges sehr nützlich. Profis hingegen werden nicht viel damit anfangen können.

Montag, 14. April 2008

The underlying connection was closed

System.Net.WebException: The underlying connection was closed: A connection that was expected to be kept alive was closed by the server. at System.Web.Services.Protocols.WebClientProtocol.GetWebResponse(WebRequest request) at System.Web.Services.Protocols.HttpWebClientProtocol.GetWebResponse(WebRequest request) at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters) at <my webservice> ....

Das Problem ist meist Serviceseitig zu suchen, durch irgendwelche verkorksten oder properitären, veralteten, etc. Einstellungen. Dummerweise hat man meistens keinen Einflußmöglichkeit auf diesen Service, also muss ein Workaround her.

Als erster Anlaufpunkt kann man versuchen den KeepAlive auf false zu setzen. Denn unter .NET 1.1 war KeepAlive noch defaultmäßig auf false, ab 2.0 auf true. Das steuert ob die WebService Verbindung dauerhaft offen gehalten werden soll, nur dumm wenn der Webserver von alleine irgendwann dicht macht nach einer gewissen Dauer, bzw. idle time.

Funktioniert das nicht, kann man noch versuchen die HttpVersion auf 1.0 runter zu setzen für mehr Kompatibilität.

Desweiteren könnte es noch helfen Expect100Continue auf false zu ändern. Dies bewirkt, dass bei einem Request gleich die Daten mit dem Header geschickt werden. Bei true (Default) wird nur der Header geschickt und die Daten erst, wenn ein "100 Continue" Response vom Server erfolgte.

   1:  namespace MyWebServiceNamespace
   2:  {
   3:      partial class MyWebServices_MyMethod
   4:      {
   5:          protected override System.Net.WebRequest GetWebRequest(Uri uri)
   6:          {
   7:              System.Net.HttpWebRequest request =
   8:                  (System.Net.HttpWebRequest)base.GetWebRequest(uri);
   9:   
  10:              // fix "underlying connection was closed.."
  11:              request.KeepAlive = false;
  12:              request.ProtocolVersion = System.Net.HttpVersion.Version10;
  13:              request.ServicePoint.Expect100Continue = false;
  14:              ...
  15:          }
  16:      }
  17:  }

Anmerkung: Durch das partial class (ab .NET 2.0) kann man das wunderbar in eine eigene Datei verlagern, getrennt vom generierten Proxy und somit unabhängig wenn neu generiert werden muss.

Freitag, 11. April 2008

TextBox und "\n"

Ja, ich weiß, eigentlich ist das nicht wirklich "sauber", aber manchmal will man nur was schnell "reinrotzen" in seine TextBox als Ausgabe und ein "\n" bringt einem so eine eckige Box reingemalt, aber nicht den erwünschten Zeilenbruch.

Die TextBox funktioniert etwas anders unter Windows und verlangt nach einem: \r\n; und schon haben wir unseren Zeilenumbruch...

...aber bevor man rumprobiert, sollte man doch einfach einmal sauber bleiben und einfach System.Environment.NewLine verwenden, das funktioniert auch und das sauber 8-)

Donnerstag, 10. April 2008

CRM 4.0 Performance Toolkit

Noch nicht getestet, aber einen Eintrag wert.
Vom Microsoft CRM Team:
http://www.codeplex.com/crmperftoolkit

Bietet Tools an und sammelt Daten über die Loadbalance, sprich die bewegten Datenmengen um Aussagen zu liefern wo das CRM System auf dem aktuellen System in die Knie gezwungen wird.

CRM SDK 4.0.4

Seit Dienstag gibt es eine neue SDK Version für CRM 4.0 von Microsoft: http://blogs.msdn.com/crm/archive/2008/04/08/microsoft-dynamics-crm-sdk-version-4-0-4.aspx

? unter C# und .NET 2.0

Jeder kennt - denke ich - den folgenden Ausdruck, der eine if/else Abfrage auf das Wesentliche komprimiert:

   1:  public void xyz(string save)
   2:  {
   3:      bool decide = save.ToLower() == "j" ? true : false;
   4:      ...
   5:  }

Ab .NET 2.0 geht aber noch mehr mit ?:

   1:  public void xyz(string save)
   2:  {
   3:      bool? decide = null;
   4:      ...
   5:  }

Huh? Eine null Zuweisung auf einen Value Type? Nein es ist ein Referenztyp in diesem Fall, da es sich der Generics unter 2.0 bedient, das in diesem Fall wie folgt definiert ist:

System.Nullable<t> wofür wiederum ein T? definiert wurde.

Das ist doch schon mal cool, aber wie stell ich fest ob es sich um einen Value oder Referenztyp handelt wenn ich einen solchen Wert zugeschoben bekomme?

   1:  int? i = 0;
   2:   
   3:  Type t1 = typeof(int?);    // System.Nullable...System.Int32
   4:  Type t2 = i.GetType();     // System.Int32
   5:   
   6:  bool eq = t1 == t2;        // false

Also das funktioniert offenbar schon einmal nicht! Und schlägt auch noch fehl, falls i == null gesetzt wurde.
Aber wir können uns folgendermaßen behelfen:

   1:  int x = i.HasValue ? (int)i : 0;

...und dafür gibt es gleich einen noch viel kürzeren neuen Ausdruck ab 2.0:

   1:  int x = i ?? 0;

Um den Generic zu bestimmen geht man wie folgt vor:

   1:  bool eq = t1.GetGenericTypeDefinition() == typeof(Nullable<>);

Doch Vorsicht! Wäre t1 nicht vom Type Generic, bekommen wir eine InvalidOperationException! Daher muß vorher noch abgeprüft werden ob es sich überhaupt um einen Generic handelt und da C# linksassoziativ auflöst, können wir das wie folgt zusammenstöpseln:

   1:  bool eq = t1.IsGenericType && t1.GetGenericTypeDefinition() == typeof(Nullable<>);

Mittwoch, 9. April 2008

Problem mit References in einer BizTalk Orchestration

Bei mir ist vorhin ein ziemlich seltsames Phänomen aufgetreten, das ich nicht eindeutig dem BizTalk Plug-In oder VisualStudio 2005 zuordnen kann. Da mir das aber noch nie unter "normalem" VS passierte rechne ich es eher Ersterem zu.

Ich hab aus einem alten BizTalk Projekt eine Orchestration übernommen. Das hat bisher auch immer wunderbar funktioniert, nach dem alles gerade gezogen wurde.

Diese Orchestration verwendet ein paar Funktionaliäten, die in einem anderen Projekt referenziert werden. Versuchte ich dies einzubinden, zeigt mir der Dialog nichts weiter an als die Standard Referenzen.... Ein Gegencheck der anderen Orchestrations ergab das die benötigten Referenzen einwandfrei in meinem Projekt erreichbar sein müßten.

Alle Refreshs und Compile-Versuche brachten nichts. Die neue Orchestration weigerte sich standhaft, die Referenzen in den entsprechenden Dialogen anzuzeigen.

Umso witziger irgendwie der Workaround:
mit dem XMLEditor in VS (Rechtsklick->Open With..) wurden ein paar der "alten" Referenzen ausgetauscht mit den neuen per Hand... und tadaaa plötzlich wurden auch die Referenzen in der Orchestration wieder allesamt ohne Murren angezeigt... äußerst seltsam irgendwie.

Da ich noch ein paar weitere ältere Orchestrations migrieren muß, werde ich das Phänomen mal im Auge behalten und untersuchen ...

PS: thx @ teratom ;)

SP mit dem Ergebnis eines SELECTs als Parameter aufrufen

Als kleine Gedächtnisstütze für mich, da ich jedesmal wieder vergesse, wie SQL funktioniert:

   1:  DECLARE @helper nvarchar(50)
   2:  SET @helper = (SELECT [Value] FROM dbo.[Tabelle] WHERE [id] = 1)
   3:   
   4:  EXEC dbo.EineSP @param1 = 'einWert'
   5:                , @param2 = @helper
   6:                , @param3 = 'nochEinWert'
   7:                , @param4 = 'undNochEinWert'

Zuerst muss eine Hilfsvariable (@helper) erstellt werden, die den gleichen Datentyp hat, wie das auszulesende Feld und der Parameter, für den sie übergeben werden soll. Anschließend wird ihr der gewünschte Wert mit Hilfe einer SELECT-Anweisung zugewiesen, wodurch sie dann einfach als Wert für den entsprechenden Parameter der Stored Procedure verwendet werden kann.

Gar nicht schwierig, trotzdem weigert sich mein Hirn immer wieder, sich das zu behalten.

Dienstag, 8. April 2008

Integration FxCop in VisualStudio2005

FxCop ist ein Prüftool um sicherzustellen das das die Richtlinien von Microsoft während des codens eingehalten werden, bzw. um zu überprüfen ob diese eingehalten wurden.

Es gibt mehrere Installationsmöglichkeiten. Zwei möchte ich hier vorstellen.

0. Pfade bitte an Eure Installation anpassen.
1. Nach der Installation von FxCop in VisualStudio im Menue "Tools" das Untermenue "External Tools..." auswählen.
2. Auf "Add" klicken.
3. In "Title:" 'FxCop' eingeben.

Ausgabe im "Output" Window von VisualStudio:
4. In "Commands:" das installierte FxCop mit "C:\Program Files\Microsoft FxCop 1.36\FxCopCmd.exe" anwählen.
5. In "Arguments:" '/c /f:"$(TargetPath)" /r:"C:\Program Files\Microsoft FxCop 1.36\Rules" /consolexsl:"C:\Program Files\Microsoft FxCop 1.36\Xml\VSConsoleOutput.xsl"' eingeben.
6. In "Initial directory:" den Pfad vom installierten FxCop eingeben (bei mir 'C:\Program Files\Microsoft FxCop 1.36').
7. "Use Output window" setzen.
8. "Apply" & "OK" anklicken.

Öffnen eines FxCop Fensters mit aktuellem Projekt:
4. In "Commands:" das installierte FxCop mit "C:\Program Files\Microsoft FxCop 1.36\FxCop.exe" anwählen.
5. In "Arguments:" '$(TargetPath)' eingeben.
6. In "Initial directory:" den Pfad vom installierten FxCop eingeben (bei mir 'C:\Program Files\Microsoft FxCop 1.36').
7. "Apply" & "OK" anklicken.

Das war es schon. Um nun zu sehen ob das eigene Projekt den Microsoftstandarts entspricht, einfach im Menue "Tools" den neuen Punkt "FxCop" anwählen (bei der "Output"-Version muss das Output Fenster (im Menuepunkt "View" aktivierbar) aktiv sein).

Downloadlink: FxCop

Abstände in Forms

Wenn man ein Control, beispielsweise eine PictureBox, in einem WinForm so ausrichten will, dass lediglich dieses zu sehen ist (abgesehen des Menu- und Status-Strips), steht man vor einigen kleineren Schwierigkeiten.
Zwar kann man mit einigen festen Zuweisungen das Problem aus dem Weg räumen, allerdings klappt das nur für ein spezielles Windows-Theme. Ist bei einem anderen Theme beispielsweise die Titelleiste höher, wird die PictrueBox nicht mehr komplett angezeigt.
Zum Glück für alle, die vor einem solchen Problem stehen, gibt es die Klasse SystemInformation im Namespace System.Windows.Forms.
Diese Klasse enthält einige nützliche Properties, die bei der perfekten Ausrichtung eines Controls sehr behilflich sein können. Hierzu gehören

Natürlich enthält die Klasse noch viele weitere Properties für andere Gebiete, die beizeiten sehr gute Dienste leisten können.  Am Besten macht sich jeder selbst ein Bild davon, ich denke, jeder kann einigen Nutzen aus SystemInformation ziehen.

Probleme mit SSO (Single Sign On) und Custom BizTalk Adapter

Beim registrieren eines Custom Adapters in BizTalk 20006 hab ich folgenden Fehler bekommen:

Cannot perform encryption or decryption because the secret is not available from the master secret server. See the event log (on computer 'XYZ') for related errors.
Des Rätsels Lösung war schnell gefunden - Ich mußte das SSO Master Secret wiederherstellen, Anleitung siehe hier:
http://msdn2.microsoft.com/en-us/library/aa560589.aspx

Einfach SSO Administration starten, Rechtsklick auf System und dann Restore mitsamt dem Passwort ...wenn hoffentlich noch zur Hand ;)

Keine Angst vor Delegates

Delegates solltem jedem C/C++ Entwickler als Funktionszeiger / Functionpointer bekannt sein. Unter C# ist die Abstraktion hierfür deutlich größer und daher wahrscheinlich auch das Verständnis selbst.

Um es kurz zusammenzufassen, ein Delegate ist eine Referenz auf eine Methode, d.h. nichts weiteres, als dass ich einer Methode eine andere als Übergabeparameter geben könnte. Oder man kann es auch als "Abkürzung" zu statischen Methoden einer anderen Klasse missbrauchen.

Beispiel Abkürzung:

   1:  namespace Geometry
   2:  {
   3:      public delegate double Trigometric(double a);
   4:   
   5:      public class XY
   6:      {
   7:          // in .NET 1.1 it is: Trigometric cos = new Trigometric(Math.Cos);
   8:          private Trigometric cos = Math.Cos; // possible in .NET 2.0+
   9:          private Trigometric sin = Math.Sin;
  10:   
  11:          ...
  12:          int x = (int)Math.Round(cos(30.0d));
  13:      }
  14:  }


Beispiel Übergabeparameter:

   1:  namespace Geometry
   2:  {
   3:      public delegate double Trigometric(double a);
   4:   
   5:      public class XY
   6:      {
   7:          // in .NET 1.1 it is: Trigometric cos = new Trigometric(Math.Cos);
   8:          private Trigometric cos = Math.Cos;
   9:          private Trigometric sin = Math.Sin;
  10:   
  11:          ...
  12:          public double CalculateAngle(Trigometric t, double angle)
  13:          {
  14:              return t(angle); // == return Math.Cos(angle) bzw. Math.Sin(angle)
  15:          }
  16:   
  17:          public void Test()
  18:          {
  19:              double angle = 30.0d;
  20:              double result = CalculateAngle(cos, angle);
  21:              
  22:              ...
  23:          }
  24:      }
  25:  }


Simpel, nicht wahr?

Freitag, 4. April 2008

Mit dotNet drucken... ein Erfahrungsbericht

Das Drucken einer Datei mit dotNet ist eigentlich keine all zu große Herausforderung. Beispiele findet man wie Sand am Meer, der eigentliche Code ist auch nicht all zu komplex.
Entsprechend schnell hat die Methodik auch funktioniert, als sie lokal in einem UnitTest aufgerufen wurde. Gedruckt wurde dabei auf einen Netzwerkdrucker. Der gleiche Code sollte aber nun als Assembly im SQL Server 2005 abgelegt werden, so dass im Prinzip nur eine Stored Procedure aufgerufen werden muss, um - in diesem speziellen Fall - einen bestimmten Report aus den Reporting Services zu drucken. Und was kommt wohl jetzt? Richtig, es hat nicht funktioniert.
Nach mehreren erfolglosen Versuchen, die Druckfunktion für den Druck auf einem Netzwerkdrucker zu realisieren, kam dann die Idee, den Drucker direkt via USB mit der Entwicklungs-VM zu verbinden. Nach Tagen des Misserfolgs dann die Überraschung: auf einem lokalen Drucker wird der Report ausgegeben.

Meine Vermutung geht zur Zeit dahin, dass dem Aufruf für einen Netzwerkdrucker noch der zu verwendende Port angegeben werden muss. Beim UnitTest muss dies nicht geschehen, weil der Aufruf hier direkt aus Windows kommt. Wird jedoch die SP aus dem SQL Server aufgerufen, kommt der Aufruf für das unterliegende OS aus dem Web, was offensichtlich einen Unterschied macht.
Diese Vermutung muss noch durch ein wenig Research und Trial and Error verifiziert werden, doch bin ich zuversichtlich, der korrekten Lösung auf der Spur zu sein.

Sobald ich genaueres weiß, seid ihr die Ersten, die es erfahren.

EDIT: Des Rätsels Lösung ist gefunden!
Anders, als ich es vermutet habe, ging die Lösung doch, wie von Tropensturm vermutet, in Richtung Benutzer-Authentifizierung.
Da die Assembly im SQL Server aufgerufen wird, greift sie auch mit dessen Rechten auf das Netzwerk zu. Offenbar besteht hier allerdings nicht das Recht, auf Netzwerkdrucker zuzugreifen. Deshalb holen wir uns die Rechte des aktuell angemeldeten Windows-Benutzers. Das geschieht folgendermaßen.

   1:  // Vorbereitung
   2:  System.Security.Principal.WindowsIdentity clientId = null;
   3:  System.Security.Principal.WindowsImpersonationContext impersonatedUser = null;
   4:   
   5:  // Windows-Identität ermitteln
   6:  clientId = SqlContext.WindowsIdentity;
   7:   
   8:  try
   9:  {
  10:      // Windows-Berechtigungen holen
  11:      impersonatedUser = clientId.Impersonate();
  12:   
  13:      // An dieser Stelle drucken
  14:  }
  15:  finally
  16:  {
  17:      if(impersonatedUser != null)
  18:      {
  19:          // Windows-Rechte wieder abgeben
  20:          impersonatedUser.Undo();
  21:      }
  22:  }

Und kaum macht man es richtig, funktioniert es... ;)

Die verlorenen Abfragen oder "Akzeptiere meine Autorität!"

Wir wissen alle, dass es Systeme auf der Welt gibt, die es uns einfach nicht erlauben WOLLEN sie korrekt zu bedienen. Eines davon scheint BizTalk2006 zu sein.

Es sollte eigentlich alles ganz einfach sein:
Ich hole mir Daten, transformiere diese anhand eines Schemas in eine Message und kann dann ganz einfach über xpath auf die enstandene XML Struktur zugreifen. Wie in diesem Beispiel, wo zwei Werte miteinander verglichen werden sollen.

   1:  stringIst == xpath(Message1, "string(/*/*[local-name()='VergleichsTag'])");

Denkste. Benutzt man nämlich ein "Decide" Shape in BizTalk2006, um mit diesem im "Rule Branche" anhand des obigen Vergleiches den Programmablauf zu steuern, wird der Vergleich einfach ignoriert. Es gibt keine Fehlermeldung, es wird nichts angemäkelt, es geht einfach nicht.
Die Abfrage wird einfach immer falsch sein und deshalb wird das Programm auch niemals diesen Zweig des Programmablaufes ausführen.

Die Lösung ist so einfach wie die eigentliche Problematik des ursprünglichen Vorgehens unverständlich.
Mann MUSS vorher den Wert aus der XML Struktur einer Stringvariable zuzuweisen und hat dann die funktionierende Möglichkeit, die beiden Strings zu vergleichen. Wobei das nicht im "Decide" gemacht werden kann, sondern seperat vorher in einem "Expression" Shape erledigt werden muss, aber das nur am Rande.

EXPRESSION - SHAPE

   1:  stringSoll = xpath(Message1, "string(/*/*[local-name()='VergleichsTag'])");


DECIDE_RuleBranch - SHAPE

   1:  stringIst == stringSoll

So klappt das dann auch mit Ihrem Kunden.

Comment in BizTalk Workaround

Einer der Punkte in Biztalk, die mich am meisten nerven, ist die Tatsache, dass es keinen "Commentaryshape" gibt für Kommentare.
Da wir meistens sehr komplexe Projekte haben, treibt mich das oft in den Wahnsinn.

Doch durch einen Zufall hab ich einen Workaround entdeckt, und zwar einen mit dem ich sehr gut leben kann (und "Workarounds" hab ich schon einige gesehen).

Man benötigt eigentlich nichts weiter als einen Expression Shape, schreibt darin seine Kommentare im normalen C#-Stil und setzt am Ende ein ";"! Das ganze benenne ich dann äußerlich einheitlich Comment und fertig (übrigens der TODO darunter ist nach dem gleichen Muster gestrickt)

Keine roten Ausrufezeichen, kein Gemecker und Gejammer 8-)
Ich finds gut!

Pickliste unter CRM 4.0 - Create (Teil 2)

Im ersten Teil habe ich das Create selbst beschrieben, das eigentlich sehr simpel von statten geht. Für ein Create benötigt man jedoch Daten zum Anlegen.
Damit beschäftigt sich der 2. Teil meines kleinen Blogexkurses.

Als erstes benötigen wir die Definitionen der Language Codes, die hab ich mal nach der SDK Vorlage erzeugt.

   1:  public sealed class LanguageCode
   2:  {
   3:    public const int German = 1031;
   4:    public const int English = 1033;
   5:   
   6:    private LanguageCode() {} // do nothing
   7:  }

Es empfiehlt sich das Erzeugen der Metadaten auch in eine eigene Methode zu kapseln.

   1:  public CRMPicklist.MetadataService.PicklistAttributeMetadata CreatePicklistMetadata(
   2:      Microsoft.Crm.SdkTypeProxy.EntityName entity,
   3:      string picklistName,
   4:      string labelName
   5:      string[] labels,
   6:      int languageCode)
   7:  {
   8:      CRMPicklist.MetadataService.PicklistAttributeMetadata pam =
   9:          new CRMPicklist.MetadataService.PicklistAttributeMetadata();
  10:   
  11:      // set base properties
  12:      pam.SchemaName = inPicklistName;
  13:      pam.DisplayName = CreateSingleLabel(labelName, languageCode);
  14:      pam.EntityLogicalName = entity.ToString();
  15:      pam.LogicalName = "new_action";
  16:      pam.Description = CreateSingleLabel("Picklist Attribute", languageCode);
  17:      pam.AttributeType = new CRMPicklist.MetadataService.CrmAttributeType();
  18:      pam.AttributeType.Value =
  19:          CRMPicklist.MetadataService.AttributeType.Picklist;
  20:   
  21:      // set access level
  22:      pam.RequiredLevel = 
  23:          new CRMPicklist.MetadataService.CrmAttributeRequiredLevel();
  24:      pam.RequiredLevel.Value = 
  25:          CRMPicklist.MetadataService.AttributeRequiredLevel.None;
  26:   
  27:      // picklist is valid for updating
  28:      CRMPicklist.MetadataService.CrmBoolean update = 
  29:          new CRMPicklist.MetadataService.CrmBoolean();
  30:      update.Value = true;
  31:      pam.ValidForUpdate = update;
  32:   
  33:      // build picklist options
  34:      CRMPicklist.MetadataService.Option[] plOptions = 
  35:          new CRMPicklist.MetadataService.Option[labels.Length];
  36:      CRMPicklist.MetadataService.Option plOption;
  37:   
  38:      // for backwards compatibility with CRM 3.0, all new picklists in CRM 4.0 
  39:      // start with 200.000
  40:      offset = 200.000;
  41:   
  42:      // fill with labels[] data
  43:      for (int i = offset; i < offset + labels.Length; i++)
  44:      {
  45:          plOption = new CRMPicklist.MetadataService.Option();
  46:          plOption.Value = new CRMPicklist.MetadataService.CrmNumber();
  47:          plOption.Value.Value = i;
  48:          plOption.Label = CreateSingleLabel(labels[i - offset], languageCode);
  49:          plOptions[i - offset] = plOption;
  50:      }
  51:   
  52:      // set extended properties
  53:      pam.Options = plOptions;
  54:   
  55:      return pam;
  56:  }

Anmerkung:
labelName sollte die Beschreibung wiedergeben im CRM System, z.B. "Address 1: Test Attribute".
entity ist die Entität in der wir die neue Pickliste anlegen wollen (z.B. contacts).

Feedburner aktiviert

Hi Leute, hab jetzt FeedBurner aktiviert (ist zum tracen von Abonnements von RSS Feeds).

http://feeds.feedburner.com/JustACodeTeamBlog

Kann man ja schön in Blogspot integrieren. Mach ich gleich mal :)

Donnerstag, 3. April 2008

Wie beende ich eine Applikation richtig?

Aus aktuellem Anlass hier mal etwas sehr grundlegendes, was einem in manchen Situationen aber absolut nicht einfallen will - eventuell, weil es zu einfach ist? ;)

Problemstellung:
Wir haben innerhalb eines FormLoad-EventHandlers eine Verzweigung, die entweder den EventHandler rekursiv aufruft, oder die Applikation beendet.

Lösungsansatz:
Für den Fall, dass die Applikation beendet werden soll, wird einfach ein Application.Exit() aufgerufen. Klingt ja so weit schön einfach. Wird aber ein folgendes Konstrukt zusammengestöpselt, erleben wir eine Überraschung.

   1:  Form1_Load(object sender, EventArgs e)
   2:  {
   3:      int retry = 0;
   4:   
   5:      try
   6:      {
   7:          string s = IrgendeinAufruf();
   8:      }
   9:      catch(Exception)
  10:      {
  11:          retry++;
  12:   
  13:          if(retry < 4) // 3 Versuche
  14:          {
  15:              Form1_Load(sender, e);
  16:          }
  17:          else
  18:          {
  19:              Application.Exit();
  20:          }
  21:      }
  22:   
  23:      MacheIrgendwasMitEinemString(s);
  24:  }

Hier werden wir eine NullReferenceException erhalten, denn das Application.Exit() wirkt nicht sofort, was dazu führt, dass die Methode 'MacheIrgendwasMitEinemString' auch noch ausgeführt wird, wobei der String natürlich nicht initialisiert wurde.

Die Lösung des Problems ist ein kleines, süßes, nützliches, gewöhnliches 'return', wodurch der korrekt funktionierende Code dann so aussieht:

   1:  Form1_Load(object sender, EventArgs e)
   2:  {
   3:      int retry = 0;
   4:   
   5:      try
   6:      {
   7:          string s = IrgendeinAufruf();
   8:      }
   9:      catch(Exception)
  10:      {
  11:          retry++;
  12:   
  13:          if(retry < 4) // 3 Versuche
  14:          {
  15:              Form1_Load(sender, e);
  16:          }
  17:          else
  18:          {
  19:              Application.Exit();
  20:              return;
  21:          }
  22:      }
  23:   
  24:      MacheIrgendwasMitEinemString(s);
  25:  }

Schlußfolgerung:
Würdigt das RETURN, hegt es, pflegt es und vor allem: nutzt es da, wo es nötig ist! ;)

BizTalk Deployment Probleme

Wer kennts nicht als BizTalk Entwickler? Kaum wird das Projekt mal ein bischen Komplex hakts mit dem Undeployen. Irgendwo in den untiefen des ewigen Internets hab ich dann eine effektive Lösung gefunden.

Some items in the removed assembly are still being used by items not defined in the same assembly, thus removal of the assembly failed. Make sure that items in the assembly you are trying to remove fulfill the following conditions: 1. Pipelines, maps, and schemas are not being used by Send Ports or Receive Locations 2. Roles have no enlisted parties. ... Undeployment failed.

Anstatt ewig zu versuchen, was denn nun blockieren könnte und alles einzeln durchzuprobieren, gibt es einen "Trick"(?) mit der SQL Datenbank, um herauszufinden welche sogenannten Referenzen uns in die Suppe spucken (wobei sich mir durchaus die Frage stellt wieso BizTalk nicht einfach das Ergebnis dieses Selects selber anzeigen kann).

SQL Datenbank... ja aber welche?
Das kann man einfach in der Registry nachsehen unter:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\BizTalk Server\3.0\Administration\MgmtDbServer

Dann den SQL Query Analyzer öffnen mit der Verbindung auf unsere gefundene Datenbank und folgenden großen SELECT absetzen:

   1:  select
   2:  'RcvPort' PortType,
   3:  r.nvcName Port,
   4:  item.name MapName,
   5:  assem.nvcName Assembly,
   6:  nSequence, indoc_docspec_name, outdoc_docspec_name
   7:  from bts_receiveport_transform rt
   8:  inner join bts_receiveport r
   9:  on rt.nReceivePortID = r.nID
  10:  inner join bt_mapspec ms
  11:  on ms.id = rt.uidTransformGUID
  12:  inner join bts_assembly assem
  13:  on ms.assemblyid = assem.nID
  14:  inner join bts_item item
  15:  on ms.itemid = item.id
  16:  --order by Port, nSequence
  17:   
  18:  union
  19:   
  20:  select
  21:  'SendPort' PortType,
  22:  r.nvcName Port,
  23:  item.name MapName,
  24:  assem.nvcName Assembly,
  25:  nSequence, indoc_docspec_name, outdoc_docspec_name
  26:  from bts_sendport_transform rt
  27:  inner join bts_sendport r
  28:  on rt.nSendPortID = r.nID
  29:  inner join bt_mapspec ms
  30:  on ms.id = rt.uidTransformGUID
  31:  inner join bts_assembly assem
  32:  on ms.assemblyid = assem.nID
  33:  inner join bts_item item
  34:  on ms.itemid = item.id
  35:   
  36:  order by PortType, Port, nSequence

Der Result Set ist eine Liste mit noch referenzierenden Objekten.

Invalid argument.

.NET bringt wirklich viele Annehmlichkeiten mit sich, die das Leben eines Programmierers drastisch erleichtern können. Aber manchmal fragt man sich schon was die .NET Entwickler sich dabei gedacht haben. Wie gerade heute wieder.

Ich hatte diese zwei unscheinbaren Zeilen im Code um damit eine XML-Struktur zu füllen.

   1:  double dblSource = 0.0000001;
   2:  string strDestination = dblSource.ToString();

Doch oh Schreck, wieso steht denn da '1E-06' im XML anstelle meines '0.0000001'?
Vor allem da dieser Wert mit dem XML einem Datenbanksystem übergeben wird und dieses mit einem schönen "Invalid argument." aussteigt.

OK, dann verwenden wir halt einen FormatString und ändern entsprechend die zweite Zeile.

   1:  string strDestination = dblSource.ToString("F20"); //20 um kleinere Zahlen abzudecken

Doch wie sieht den jetzt mein String aus? Jetzt habe ich '0.00000010000000000000'.
Naja, jetzt könnte man natürlich eine Schleife coden die einem den String so lange kürzt, bis hinten keine '0' mehr dran steht (Ähmm... ich kenne zufällig jemanden der das auch gleich mal umgesetzt hat ;) ), aber es gibt auch eine etwas elegantere Lösung (thx @ Tropensturm).

   1:  double dblSource = 0.0000001;
   2:  decimal decTemp = (decimal)dblSource;
   3:  string strDestination = decTemp.ToString();

Ergebnis ist ein String mit dem Wert '0.0000001'

Double Werte nach String oder "E-09"

   1:  double db = 0.000000001;      // == 1E-09
   2:  decimal dec = (decimal)db;    // == 0.000000001M;
   3:   
   4:  string sDB = db.ToString();   // == "1E-09" -> bloed
   5:  string sDEC = dec.ToString(); // == "0.000000001" -> gut ;)

Bitte mit try/catch anwenden, ab sehr großen Exponenten in der Region um 30 ist Schluß und wir kriegen eine Overflow Exception beim expliziten Cast.

Integration von CLR in den SQL Server

Der Link der Links, wenn es darum geht, wie man CLR in den SQL Server integriert.
Alles, was man wissen muss, anschaulich und einfach dargelegt.

Ich wünschte, ich hätte den Link gefunden, bevor ich das alles selbst herausgefunden habe...

SQL Server DBA Guide to SQLCLR

Pickliste unter CRM 4.0 - Create (Teil 1)

Seit CRM 4.0 kann man mit Hilfe des MetadataService Picklisten bearbeiten. Eine Pickliste ist eine Liste von Options die in einer Entität verankert ist (es ist wichtig zu wissen, dass eine Picklist immer zu einer Entität gehört).
Am ehesten lässt sich eine Pickliste mit einer Auswahlliste vergleichen. Eine Option spiegelt den einzelenen Eintrag innerhalb einer Pickliste wieder, wobei die Option aus einer laufenden Nummer und einem Label besteht.
Das Label selbst ist der einzelene "Eintrag" den man später in der Pickliste auswählen kann + (!) einem Ländercode (Anmerkung: dieser Ländercode muss in unserem CRM aber auch bekannt sein!).
Das heißt: je nach eingestellter Sprache wird dann entsprechend die Pickliste später eine Auswahl bereitstellen.

Für uns interessante Ländercodes:
Deutsch = 1031
Englisch = 1033

Beschäftigt man sich mit dem SDK, findet man ein Beispiel "MultiLanguagePicklist.cs", das sollte einem schon einmal einen guten Einstieg geben in diese neue Thematik.

Man sollte sich nicht verwirren lassen, dass man ähnliche Funktionalitäten finden kann, sowohl unter dem MetadataService als auch dem CRMService und dem SDK.
Wir wollen eine Pickliste bearbeiten also -> MetadataService!

Das Create funktioniert an sich sehr einfach. Hat man erstmal die Metadaten für die Pickliste erzeugt, kann man das schön und einfach wegkapseln:

   1:  //the service instance
   2:  private CRMPicklist.MetadataService.MetadataService _serviceMeta;
   3:   
   4:  public Guid CreatePicklist(CRMPicklist.MetadataService.PicklistAttributeMetadata pam, CrmSdk.EntityName inEntity)
   5:  {
   6:      CRMPicklist.MetadataService.CreateAttributeRequest createRequest = new CRMPicklist.MetadataService.CreateAttributeRequest();
   7:      createRequest.EntityName = inEntity.ToString();
   8:      createRequest.Attribute = pam;
   9:   
  10:      try
  11:      {
  12:          CRMPicklist.MetadataService.CreateAttributeResponse createResponse;
  13:   
  14:          // execute the request
  15:          createResponse = (CRMPicklist.MetadataService.CreateAttributeResponse)_serviceMeta.Execute(createRequest);
  16:   
  17:          // return GUID
  18:          return createResponse.AttributeId.Value;
  19:      }
  20:      catch (System.Net.WebException wex)
  21:      {
  22:          // Exception-Handling
  23:      }
  24:      catch (System.Web.Services.Protocols.SoapException sex)
  25:      {
  26:          // Exception-Handling
  27:      }
  28:   
  29:      return Guid.Empty; // nothing created
  30:  }

Zum besseren Verständnis sind die Namespaces ausgeschrieben.
Eine Anmerkung zum Code noch: Es ist ganz guter Stil WebException und SoapExceptions an dieser Stelle abzufangen und weiter zu verarbeiten, anstatt immer generell alles mit Exception zu erschlagen auf unterster Ebene. Das Bedarf natürlich zu verstehen was man tut und was dies für Auswirkungen haben kann im worst case.

Nach dem Create benötigt man noch ein Publish, denn ohne Publish ist die Pickliste nicht für die Entität verfügbar, obwohl enthalten. Es ist, anders gesagt, ein "release die neue Version unserer Pickliste" wenn das verständlicher sein sollte.

   1:  // our crm service
   2:  private CRMPicklist.CrmService.CrmService _service;
   3:  static private IFormatProvider _provider = System.Globalization.CultureInfo.CurrentCulture;
   4:   
   5:  // publishing
   6:  static private string _pubHeader = "<importexportxml>";
   7:  static private string _pubEntity = "<entities><entity>{0}</entity></entities>";
   8:  static private string _pubFooter = "<nodes/></importexportxml>";
   9:   
  10:  public void PublishPicklist(CrmSdk.EntityName inEntity)
  11:  {
  12:      CRMPicklist.CrmService.PublishXmlRequest pubRequest = new CRMPicklist.CrmService.PublishXmlRequest();
  13:      string xml = _pubHeader + string.Format(_provider, _pubEntity, new object[]{inEntity.ToString()}) + _pubFooter;
  14:      pubRequest.ParameterXml = xml;
  15:   
  16:      try
  17:      {
  18:          CRMPicklist.CrmService.PublishXmlResponse pubResponse = (CRMPicklist.CrmService.PublishXmlResponse)_service.Execute(pubRequest);
  19:      }
  20:      catch (System.Net.WebException wex)
  21:      {
  22:          // Exception-Handling
  23:      }
  24:      catch (System.Web.Services.Protocols.SoapException sex)
  25:      {
  26:          // Exception-Handling
  27:      }
  28:  }


Anmerkung 1: Das erzeugen des XML für das Publishing kann man sicherlich schöner machen und dient hier nur der Demonstration.
Anmerkung 2: Wir wollen die neue Version der Entität "publishen", daher verwenden wir den CRM Service jetzt!

Damit sind wir auch schon fertig mit dem Anlegen unserer neuen Pickliste.

Mittwoch, 2. April 2008

Do not modify the Quantity field when you update the primary unit.

MS-CRM3.0 bietet tolle Möglichkeiten um es programmatisch Ansteuern zu können. Eine davon ist der Zugriff über WebServices um aus Programmcode Daten neu erstellen oder bereits vorhandene Daten ändern zu können. Jedoch sind nicht alle erreichabren Daten auch änderbar...

Das Attribut "quantity" in der Entität "uom" darf auf jeden Fall nicht modifiziert werden, wenn zeitgleich das Attribut "isschedulebaseuom" auf den Wert '1' gesetzt ist und die UoM (engl. Unit of Measure für Einheit) somit als BaseUoM (Basiseinheit) deklariert ist.

Entwickeln unter Vista...

...zum Glück gibt es VirtualPC, VMWare, ... (VPC ist unter Vista deutlich besser als VMWare, da dieses leider dort noch immer viele Bugs hat)

Ich kann bisher nur davon abraten mit Vista zu entwickeln.
Aktuell verwende ich es in der Version mit SP1, aber die Anzahl der Bugs ist tödlich nervig - allein, dass mir der Speicher ständig vom InternetExplorer zugemüllt wird und ich ihn oftmals regelrecht abschießen muss.
Mit SP1 merkt man wieder, dass MS am IE herumgedreht hat, aber auch, dass noch vieles zu tun ist.
Auch das Verschieben von Dateien kann mitunter einen Datenverlust mit sich bringen... man muss sich ganz andere Verhaltensmuster angewöhnen.

Hoffentlich werde ich die auch wieder los.

Aber immerhin, in der Tat wird Vista mit jedem Bugfix spürbar erträglicher. Dennoch hab ich noch immer genügend Fehler im Alltag. Software Entwicklung bringt Systeme schnell an den Rand der Leistungsfähigkeit und offenbaren viel mehr Bugs, als bei einer "Normal"-Nutzung. Vor allem sollte ich darauf achten nächstes mal ein 64Bit OS zu verwenden, durch den Speicherhunger ist kaum noch genügend vorhanden von den 3+xGB RAM und irgendwie funktioniert der virtuelle Speicher auch nur wenn er nicht gerade seine Tage hat...

Code-Dokumentation

Ein sehr nützlicher Link, wenn man auf gute Code-Dokumentation Wert legt, z.B. weil man sich daraus gerne eine API-Dokumentation generieren möchte: Recommended Tags for Documentation Comments

Zur Generierung oben genannter API-Dokumentation ist für mich zur Zeit Sandcastle das Mittel der Wahl. Dieses ist in Verbindung mit dem Sandcastle Help File Builder absolut unverzichtbar - zumindest für die .NET-Framework Version 2.0. Eine Version für 3.0 oder gar 3.5 lässt noch auf sich warten.

Sandcastle Projektseite
Sandcastle Help File Builder Projektseite

Bug des Monats - März 2008

"Ja haben wir denn schon den 32. März? "

   1:  ...
   2:  DateTime morgen = new DateTime(
   3:      DateTime.Now.Year, 
   4:      DateTime.Now.Month, 
   5:      DateTime.Now.Day + 1, 
   6:      0, 
   7:      0, 
   8:      0);
   9:  ...

wirkt auf den ersten Blick nicht nur furchtbar verstörend, hat auch Auswirkungen am Monatsende ;)

Thx @ SHo

PS: Schön wenn man Code von echten "Könnern" debuggen darf, die eine ganz genaue Vorstellung haben, wie man richtig zu programmieren hat. Daher schon jetzt meine Lieblingskategorie auf unserem Blog: Code Bug des Monats

Links: CRM 4.0 & FXCop

Manchmal sind es einfach zu viele und ich speicher mir persönlich Webpages mit interessanten "snippets" nur noch ab um nicht in einem heilosen Chaos von unsortierten Linksammlungen zu versinken...
Jedoch sind einige Pages es wert öfters besucht zu werden...

Microsoft CRM Team Blog:
http://blogs.msdn.com/crm/
Viel Interessantes rund ums CRM.

Microsoft CRM 4.0 SDK:
http://www.microsoft.com/downloads/details.aspx?FamilyId=82E632A7-FAF9-41E0-8EC1-A2662AAE9DFB&displaylang=en
Man findet nicht viele Erklärungen (auch nicht in der MSDN) wie man denn nun insbesondere den neuen Metadatenservice anwenden kann. Da hilft das SDK schon mal über das gröbste hinweg beim Einstieg.
Übrigens wird es aktuell kontinunierlich aktualisiert, es lohnt sich also öfters mal nach einer neuen Version Ausschau zu halten.

The Code Analysis Team Blog:
http://blogs.msdn.com/fxcop/
Wer sich für den FxCop interessiert...

Streams

Schließe einen Stream erst so spät wie möglich (z.B. Dispose, Close Events, etc) aber tue es!
(...und frag ab ob er nicht schon null ist)

Streams kann man nicht mehr abspeichern oder andersweitig bearbeiten wenn sie geclosed wurden. Es ist zwar noch möglich z.B. Properties von einem Bild auszulesen, aber das speichern scheitert später unweigerlich.

Ist der Stream z.B. einmal abgespeichert worden, kümmert sich in der Regel der Garbage Collector darum. Wenn aber nichts weiteres besonderes mit dem Stream passiert ist der Stream noch immer offen beim Dispose, etc.

   1:  protected override void OnFormClosing(FormClosingEventArgs e)
   2:  {
   3:      if(stream != null)
   4:      {
   5:          stream.Close();
   6:      }
   7:   
   8:      base.OnFormClosing(e);
   9:  }