Soweit zu den Anpassungen und nun zur eigentlichen Dynamisierung des Abfrage-Intervalls der SharedMemoryDependency-Klasse. Das ganze kann nur funktionieren, wenn ein Verkürzen des Intervalls sofort geschieht und die Verlängerung in Abständen vorgenommen wird. Das es geklappt hat kann man an folgendem Video sehen.
25.07.2008 19:56:29
|
Christian Binder
24.07.2008 22:18:50
|
Thomas Schissler
24.07.2008 13:23:00
|
Jens Peter Kleinau
24.07.2008 00:07:56
|
Thomas Schissler
23.07.2008 17:52:15
|
Thomas Schissler
23.07.2008 16:49:00
|
Steffen Ritter
23.07.2008 16:03:47
|
Rainer Schuster
23.07.2008 11:55:59
|
Rainer Schuster
22.07.2008 14:58:00
|
Steffen Ritter
22.07.2008 13:37:46
|
Rainer Schuster
22.07.2008 10:19:00
|
Ozgur Aytekin
20.07.2008 01:56:13
|
Rainer Schuster
19.07.2008 17:11:38
|
Norbert Eder
18.07.2008 22:36:00
|
Ozgur Aytekin
18.07.2008 13:57:09
|
Thomas Schissler
18.07.2008 13:10:55
|
Jens Häupel
17.07.2008 12:36:00
|
Jürgen Gutsch
17.07.2008 11:42:06
|
Andre Loker
17.07.2008 10:39:35
|
Oliver Scheer
17.07.2008 10:33:51
|
Christian Binder
17.07.2008 09:38:13
|
Thomas Schissler
17.07.2008 08:04:57
|
Robert Mühsig
17.07.2008 07:35:44
|
Andre Kraemer
16.07.2008 18:33:09
|
Norbert Eder
15.07.2008 23:35:46
|
Mathias Raacke
15.07.2008 16:34:15
|
Rainer Schuster
15.07.2008 11:50:21
|
Rainer Schuster
15.07.2008 11:04:00
|
Oliver Scheer
15.07.2008 09:11:00
|
Christian Binder
14.07.2008 23:09:20
|
Robert Mühsig
14.07.2008 19:56:00
|
Ozgur Aytekin
14.07.2008 19:30:32
|
Norbert Eder
14.07.2008 10:51:29
|
Oliver Scheer
14.07.2008 08:51:13
|
Thomas Schissler
13.07.2008 17:54:27
|
Klaus Bock
Im vorherigen Artikel habe ich die Verwendung eines SharedMemory Segment mit Hilfe zweier Konsolenanwendungen gezeigt. Dort wurde in der Client-Anwendung reagiert, indem ein Objekt aus dem SharedMemory-Segment geladen und mit einem gespeicherten Objekt verglichen wurde. Das funktioniert ja mit kleineren Objekten im Shared Memory ganz gut. Doch was ist wenn eine größere DataTable oder gar ein mehre Mb großes DataSet, oder ein ähnlich komplexes Objekt, im SharedMemory gespeichert ist? Allein das laden der Objekte zum vergleichen, sowie der Vergleich an sich dürften sich auf die Reaktionsgeschwindigkeit dieser Anwendung sehr nachteilig auswirken. Zur Lösung dieses Problems bietet sich eine Dependency-Klasse, ähnlich der SqlDependency-Klasse, an. Nur wie muss die Implementierung solch einer Dependency-Klasse aussehen? Die grundlegenden Funktionen sollten sein:
- Einen Mechanismus zur Benachrichtigung des Abonnenten.
- Der Abonnent braucht keine Kenntnis vom zugrundeliegenden SharedMemory Segment zu haben.
- Der Intervall in dem das SharedMemory Segment abgefragt wird muss einstellbar sein.
- Der Benachrichtigung's-Mechanismus sollte das geänderte Objekt aus dem SharedMemory Segment liefern.
- Es sollte nur der Namen des zu überwachenden SharedMemory Segments angegeben werden müssen.
- Eine Möglichkeit das SharedMemory Segment in sehr kurzen Abständen auf Änderungen zu überprüfen.
Um den letztgenannten Punkt zu realisieren habe ich das Konzept der SharedMemory-Klasse noch einmal dahingehen überarbeitet, so dass jetzt zwei Segmente erzeugt werden. Zusätzlich zum bereits bestehenden Segment wird jetzt ein zweites Segment erzeugt, welches lediglich einen Integer-Wert speichert der den Status des Daten-Segments darstellt. Beim speichern eines Objekts in das Daten-Segment wird der Wert 1 in das Schlüssel-Segment geschrieben, welcher eine Änderung darstellt. Wird nun das Objekt von der GetObjekt() Methode abgerufen, schreibt diese den Wert 0 in das Schlüssel-Segment und kennzeichnet es so als gelesen. Solange der Abfrage-Mechanismus nun den Wert 0 liest, brauchen die Daten nicht aus dem Daten-Segment abgerufen zu werden. Das Abrufen des Integer-Wertes aus dem Schlüssel-Segment geht wirklich sehr schnell. Real weit unter einer Millisekunde. Dadurch lasse sich sehr kurze Abfrage-Intervalle realisieren. Um zu zeigen wie schnell die Dependency-Klasse auf Änderungen im Daten-Segment reagiert, habe ich ein Demo erstellt in dem in zufälligen Intervallen in das Daten-Segment geschrieben wird. Hier vorab ein kleines Video:
// neue Instanz der SharedMemoryDependency initialisieren
// mit einem Abfrage-Intervall von 15 Millisekunden
SharedMemoryDependency dependency = new SharedMemoryDependency("testing", 15);
// OnChanged Eventhandler registrieren
dependency.OnChanged +=
new EventHandler<SharedMemoryNotificationEventArgs>(DependencyOnChanged);
// prüfen ob dependency bereits gestartet wurde
if (!dependency.Enabled)
{
// dependency jetzt starten
dependency.Start();
}
Beim registrieren des EventHandler OnChanged kann automatisch ein Delegat erzeugt werden. In der Methode kann das geänderte Objekt aus dem Daten-Segment direkt als Eigenschaft des SharedMemoryNotificationEventArgs-Arguments empfangen werden.
static void DependencyOnChanged(object sender, SharedMemoryNotificationEventArgs e)
{
Console.WriteLine(" lese: " + e.ChangedContent.ToString());
}
Soweit zur Verwendung der SharedMemoryDependency-Klasse in einer Client-Anwendung.
Wie bereits oben schon angesprochen, habe ich die SharedMemory-Klasse überarbeitet um das Zusammenspiel mit der SharedMemory-Klasse überhaupt zu ermöglichen. Um den aktuellen Status eines Objekts im Speicher abfragen zu können, wurde die interne Methode GetStatus() hinzugefügt. Diese gibt, ähnlich einem HRESULT, einen von drei möglichen Integer-Werten zurück. Wobei -1 für "Objekt nicht gefunden" steht. 0 wird zurückgegeben wenn keine Änderungen vorliegen und 1 wenn das Objekt im Speicher geändert wurde. Ich habe die Methode mit dem internal Schlüsselwort versehen, da diese Methode nur intern von der SharedMemoryDependency-Klasse verwendet werden soll. Diese Methode macht nichts anderes, als den Wert aus dem angesprochenen zweiten SharedMemory Segment zu lesen und zurück zugeben.
internal int GetStatus()
{
// Rückgabewert mit -1, Objekt nicht gefunden, initialisieren
int value = -1;
// MemoryStream zum aufnehmen des Status erzeugen
MemoryStream keyStream = new MemoryStream();
// das Status-Objekt in den Stream schreiben
CopySharedMemoryToStream(keyStream, true);
// einen Formatter zum deserialisieren erzeugen
BinaryFormatter formatter = new BinaryFormatter();
try
{
// Den Rückgabewert in einen Int-Wert parsen
value = int.Parse(
formatter.Deserialize(keyStream).ToString(),
CultureInfo.InvariantCulture);
}
catch (SerializationException)
{
// Wahrscheinlich kein Objekt im Stream.
// Fehler bei der Deserialisierung abfangen
// um -1 zurückgeben zu können.
}
return value;
}
In der SharedMemoryDependency-Klasse wird genau diese Methode in einem TimerCallback-Delegaten verwendet um auf Änderungen im Speicher zu reagieren. Wenn der TimerCallback-Delegate den Wert 0 liest erfolgt keine Aktion. Liest der Delegat den Wert 1 liest, wird das geänderte Objekt aus dem SharedMemory Segment gelesen. Anschließend wird ein neuer EventHandler-Delegate erzeugt. Diesem Delegaten wird eine Instanz der SharedMemoryNotificationEventArgs-Klasse mit dem Objekt aus dem SharedMemory Segment erzeugt und das Ereignis OnChanged ausgelöst sowie die Eigenschaft HasChanged auf true gesetzt. Somit wird dem Abonnenten mitgeteilt, das eine Änderung im SharedMemory Segment vorliegt. Sollte der Delegat den Wert -1, also Objekt nicht gefunden, lesen wird der Aufrufende Timer zerstört sowie der Wert der Eigenschaft Enabled auf false gesetzt und somit die Überwachung als beendet gekennzeichnet. Hier ein Listing des Delegaten:
private void CheckSegment(object state)
{
// den aktuellen Wert aus dem Schlüssel-Segment lesen
this.segment.Lock();
int i = this.segment.GetStatus();
this.segment.Unlock();
// wenn Rückgabewert 1, würde das Objekt im
// SharedMemory Segment geändert
if (i == 1)
{
// das Objekt aus dem SharedMemory-Segment holen
this.segment.Lock();
object data = this.segment.GetObject();
this.segment.Unlock();
// EventHandler initialisieren
EventHandler<SharedMemoryNotificationEventArgs> handler = this.OnChanged;
if (handler != null)
{
// Event auslösen
handler(
this,
new SharedMemoryNotificationEventArgs(data));
}
// Änderung signalisieren
this.hasChanged = true;
}
else if (i == -1)
{
// Objekt nicht im Speicher vorhanden oder es kann nicht
// darauf zugegriffen werden.
// Den aufrufenden Timer zerstören
Timer t = (Timer)state;
t.Dispose();
// Überwachung als beendet signalisieren
this.watching = false;
// Ausnahme auslösen
throw new SharedMemoryException(
"Auf das Schlüssel-Segment im Speicher kann nicht zugegriffen werden.");
}
}
Die Umsetzung der Dependency-Klasse ist somit erfolgreich abgeschlossen. Für die Zukunft währe eine dynamische Anpassung des Abfrage-Intervalls, basierend auf den Intervallen zwischen den erkannten Änderungen im SharedMemory Segment, eine nützliche Änderung. Somit bräuchte sich um die Einstellung des Abfrage-Intervalls nicht mehr gekümmert zu werden. Falls gewünscht könnte der Abfrage-Intervall mit 0 initialisiert werden und um den Rest kümmert sich die SharedMemoryDependency-Klasse. Vorschläge und Kritiken, positive als auch negative, sind ausdrücklich erwünscht.
IpcTests_dependency
Wer bereits das Demo-Projekt aus dem vorherigen Artikel verwendet, sollte die Klassen NativeMethods und SharedMemory mit den Klassen aus diesem Demo ersetzen. Sollte jemand dieses Projekt oder Teile daraus in einem eigenen Projekt verwenden wollen, sollte er unbedingt die Klassen aus dem aktuellen Projekt verwenden. Dieses Demo und alle zukünftigen stehen unter der MIT-Lizenz. Die Modifizierung des Original-Codes von Richard Blewett ist mit ihm abgeklärt und wurde von ihm abgesegnet.
Simples Text Captcha mit Classic ASP
12.07.2008 16:47:00
|
Peter Bucher
Da es anscheinend immer noch solche gibt, die noch mit Classic ASP arbeiten, habe ich mein Captcha Beispel (Simples Text Captcha mit ASP.NET) mit Classic ASP nachgebaut.
Eines vorneweg, es war eine echte Qual, sich wieder die untypisierte, etc... Scriptsprache "VBScript" und "Classic ASP" anzutun, aber wieder mal ein wenig "Back to the roots" schadet auch nicht :-)
Da es bei Eingaben leicht zu Laufzeit-Fehlern in Classic ASP kommt, habe ich anstelle einer Prüfung - die auch fehlschlagen kann, da bspw. IsNumeric() auch Fehlerhaft ist - eine einfache Fehlerbehandlung benutzt.
Zusätzlich verwende ich jetzt ein HiddenField, damit der Label-Text nach einem PostBack wiederhergestellt wird. In ASP.NET geschieht dies automatisch mit Hilfe des ViewStates.
Und damit die Eingabe immer wieder ins Formular-Feld geschrieben wird, reicht ein einfaches (value = Request.Form(<Name>)).
Hier der Code zum studieren (Und zum Vergleich gibts hier das ASP.NET Pendant):
<% Option Explicit %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<A href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</A>">
<html xmlns="<A href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</A>">
<head>
<title>Simples Text Captcha mit Classic ASP</title>
</head>
<body>
<%
Dim s_status, _
s_captcha, _
s_hiddenCaptchaText
'// Wenn das Formular abgeschickt wurde
If Request.Form.Count > 0 Then
'// Validieren und Status anzeigen
If ValidateCaptcha() Then
s_status = "Eingaben korrekt"
Else
s_status = "Eingaben fehlerhaft"
End If
'// Wiederherstellung des aktuellen Captcha-Textes (ViewState kommt hier in ASP.NET zum Zuge)
s_hiddenCaptchaText = Request.Form("hiddenCaptchaText")
s_captcha = s_hiddenCaptchaText
Else
Dim random
Dim num1, _
num2
num1 = CreateRandomNumber(1, 15)
num2 = CreateRandomNumber(1, 15)
s_captcha = CStr(num1) & " + " & CStr(num2) & " ="
s_hiddenCaptchaText = s_captcha
Session("captcha") = num1 + num2
End If
'// Erzeugt eine Zufalls-Nummer zwischen "i_min" und "i_max"
Private Function CreateRandomNumber(i_min, i_max)
Call Randomize()
CreateRandomNumber = CInt((i_max * Rnd) + i_min)
End Function
'// Validiert das Captcha und gibt bei Erfolg "true" zurück
Private Function ValidateCaptcha()
Dim i_sessionValue, _
i_inputValue
On Error Resume Next
i_sessionValue = CInt(Session("captcha"))
i_inputValue = CInt(Request.Form("txtCaptcha"))
If Err.Number <> 0 Then
ValidateCaptcha = False
End If
On Error GoTo 0
ValidateCaptcha = (i_sessionValue = i_inputValue)
End Function
%>
<h1>Simple Captcha (Classic ASP)</h1>
<form id="captchaForm" name="captchaForm" method="post" action="default.asp">
<label for="txtCaptcha"><%= s_captcha%></label>
<input type="text" name="txtCaptcha" id="txtCaptcha" value="<%= Request.Form("txtCaptcha") %>" /><br />
<input type="hidden" name="hiddenCaptchaText" value="<%= s_hiddenCaptchaText %>" />
<input type="submit" name="btnSubmit" value="Abschicken" />
<span><%= s_status%></span>
</form>
</body>
</html>
Benutzung:
Kompletter Code in eine *.asp Datei speichern, das "action"-Attribut des Formulars auf den Dateinamen anpassen und fertig.
HowTo: Eigene .NET Events definieren und mit Unit-Tests testen
12.07.2008 01:30:15
|
Robert Mühsig
In dem heutigen HowTo geht es um das Erstellen von eigenen .NET Events samt dem dazu gehörigen testen mit einem Unit-Test.
Was ist ein Event aus “Anfängersicht”?
Jeder der (wahrscheinlich) in einer X-beliebigen IDE für eine X-beliebige Sprache bereits irgendein Button auf ein Fenster gezogen hat, wird als Resultat dann so einen ähnlichen Code sehen:
private void button1_Click(object sender, EventArgs e)
{
}
Hier kann man nun ganz genau definieren, was passiert wenn der “button1″ geklickt wird.
Eigentlich eine tolle Geschichte
Was passiert denn technisch im Grunde genommen dahinter?
Events sind von der Idee her bereits sehr alt. Der Grundgedanke ist einfach nach dem “Hollywood-Prinzip“: Ruf nicht uns an - wir rufen dich an.
Im Code brauchen wir nicht ständig prüfen ob der Button geklickt wird oder nicht - der Button sagt uns, wann er geklickt wird.
Ohne jetzt die .NET Implementierung (also was im Framework passiert) näher untersucht zu haben, würde ich meinen, dass das Grundkonzept aus dem Observer-Pattern abgeleitet ist.
Was ist ein Event: Beispiel aus der realen Welt
Um mal ein Beispiel aus der realen Welt aufzugreifen - auch wenn dies manchmal arg abstrakt ist
Wenn jemand ein “Bild-Zeitungs-Abo” hat, fragt der normal-deutsche-Leser auch nicht ständig den Herrn Springer ob es nun eine neue Ausgabe gibt oder nicht - er bekommt sein Exemplar automatisch sobald es gedruckt wurde.
Einsatzgebiet von Events
Sobald man irgendwelche “Prozesse” oder “Abläufe” modelliert, könnte man im Prinzip auf Events setzen - ich persönlich bin erst vor kurzem auf das Thema gekommen. Das liegt vor allem daran, dass ich bisher meistens mich mit Web-Projekten beschäftigt habe. In der ASP.NET Welt sind meiner Meinung nach Events nicht so unglaublich nützlich. Der Grund liegt auf dem, wie HTTP funktioniert:
Der Client macht eine Anfrage und der Server antwortet entsprechend. Rein theoretisch geht nun ein Ablauf los - der irgendwann beeendet ist - das “Ich-bin-fertig-mit-meiner-Aufgabe” könnte über ein Event mitgeteilt werden.
An dieser Stelle sollte der Server an den Client zurückschicken: Bin fertig. Allerdings geht dies nicht:
Im HTTP Umfeld kann der Server nicht einfach Daten zum Client schicken - der Client (Browser) muss immer erst die Anfrage stellen. Dadurch muss man auf der Clientseite (über AJAX z.B.) ein Polling durchführen. Das führt natürlich dazu, dass Events leicht nutzlos werden.
Allerdings sind sie in sämtlichen Client-Anwendungen die nicht auf HTTP beruhen äußerst nützlich
Eigene Events definieren
Dan Wahlin hat ein sehr schönes Video erstellt, in dem die Grundgedanken sehr gut vermittelt werden.
Mein Beispiel:
Wir haben eine bestimmte Anwendung, welche jeweils einen Verbindungsstatus haben kann:
Diese “ConnectionStates” in der Anwendung werden von einem “ConnectionManager” verwaltet.
Jetzt wäre es ja schön, wenn uns unser “ConnectionManager” sofort informiert, wenn sich der Status ändert.
Dazu erstmal das grobe Konstrukt unserer Klasse:
public class ConnectionManager
{
private ConnectionStates _state;
public ConnectionStates State
{
get
{
return _state;
}
set
{
_state = value;
}
}
public ConnectionManager()
{
this.State = ConnectionStates.Disconnected;
}
}
Der Anfangsstatus ist erstmal auf “Disconnected” gestellt. Der Rest sollte soweit klar sein.
Jetzt kommen wir zur eigentlichen Eventdeklaration.
Schritt 1: Delegat definieren
Als ersten Schritt schreiben wir uns ein Delegat:
public delegate void StateChangedEventHandler(object sender, StateChangedEventArgs e);
Ein Delegat darf man als eine Art “Funktionszeiger” (im Video von Dan Wahlin wird es auch als Pipe zwischen Objekten verglichen) verstehen.
Schritt 2: StateChangeEventArgs definieren
In unserem Delegat definieren wir eine Methodendeklaration die so später auch der Clientcode sieht - als EventArgs definieren wir ebenfalls unsere eigene Klasse:
public class StateChangedEventArgs : EventArgs
{
public ConnectionStates NewConnectionStates { get; set; }
}
Hier definieren wir einfach, was wir in unseren EventArgs später haben wollen - uns interessiert natürlich am meisten, was nun der neue Status ist.
Schritt 3: Event definieren
Jetzt definieren wir unser Event - an diesem können sich später die entsprechenden Clients melden:
public event StateChangedEventHandler StateChanged;
Dieses Event ist vom Typ “StateChangedEventHandler” - welches unser vorher definiertes Delegat ist.
Zwischenschritt: Der Client
Allein durch diese Definition des Events und des Delegates ist es möglich, das sich “Clientcode” an das Event dran hängt:
class Program
{
static void Main(string[] args)
{
ConnectionManager man = new ConnectionManager();
Console.WriteLine("Start state: " + man.State.ToString());
man.StateChanged += new ConnectionManager.StateChangedEventHandler(man_OnStateChanged);
man.State = ConnectionStates.Connecting;
man.State = ConnectionStates.Connected;
man.State = ConnectionStates.Disconnected;
Console.ReadLine();
}
static void man_OnStateChanged(object sender, StateChangedEventArgs e)
{
Console.WriteLine("State changed...");
Console.WriteLine("New state is: " + e.NewConnectionStates.ToString());
}
}
Die “man_OnStateChanged” Methode ist zwar definiert - allerdings rufen wir in unserem “ConnectionManager” nie das Event auf.
Schritt 4: Event in der Klasse aufrufen
In unserem Setter müssen wir natürlich das Event werfen - dies geschieht über eine weitere Methode in der Klasse. Hier mal der komplette Source Code:
public class ConnectionManager
{
private ConnectionStates _state;
public ConnectionStates State
{
get
{
return _state;
}
set
{
_state = value;
OnStateChanged();
}
}
public delegate void StateChangedEventHandler(object sender, StateChangedEventArgs e);
public event StateChangedEventHandler StateChanged;
protected void OnStateChanged()
{
if (StateChanged != null)
{
StateChangedEventArgs args = new StateChangedEventArgs();
args.NewConnectionStates = this.State;
StateChanged(this, args);
}
}
public ConnectionManager()
{
this.State = ConnectionStates.Disconnected;
}
}
Bei jedem setzen eines ConnectionStates wird die “OnStateChanged” Methode aufgerufen - diese ist nur intern erreichbar (”protected”) bzw. von vererbten Klassen.
Diese Methode prüft, ob das “StateChanged” Event irgendwelche Beobachter hat - if(StateChanged != null).
Falls irgendwer im Clientcode sich an das Event angehangen hat, wird das Event mit unseren EventArgs geworfen.
Es klingt komplizierter als es ist
Da ich ebenfalls “neu” darin bin, musste ich mich ebenfalls erst mal in das Einarbeiten. Um es mal in kurzen Worten zu formulieren (soweit mein Verständnis richtig ist):
- Am “event” StateChanged können sich beliebige Clienten anmelden. Der Clientcode hat die selbe Methodensignatur (object Sender, EventArgsXYZ args) wie in dem “delegat” definiert.
- Das “delegat” ist nur eine definierte Schnittstelle zwischen den Objekten. Hier wird die Methodensignatur von dem Clientcode bestimmt.
- Die “EventArgs” sind eigene Datenklassen um entsprechende sinnvolle Daten zu übermitteln wenn das Event geworfen wurde
- Die interne “OnStateChanged” Methode prüft ob irgendwas am “event” hängt - wenn ja, dann löse es aus und leite es (über das delegat) an die richtige Stelle im Clientcode.
Resultat
In der Clientanwendung (die Consolen-Applikation) wird jedesmal die Ausgabe gemacht, sobald sich der Status ändert. Ohne jedes mal eine extra Methode aufzurufen oder die Ausgabe an den Manager zu ketten:
Unit-Tests: Wie teste ich Events?
Events kann man über eine nette C# 2.0 Sache testen: ein anonyme delegate. Den Trick habe ich bei Phil Haack gefunden.
[TestMethod]
public void ConnectionManager_Raise_StateChanged_Event()
{
ConnectionManager man = new ConnectionManager();
Assert.AreEqual(ConnectionStates.Disconnected, man.State);
bool eventRaised = false;
man.StateChanged += delegate(object sender, StateChangedEventArgs args)
{
eventRaised = true;
};
man.State = ConnectionStates.Connecting;
Assert.IsTrue(eventRaised);
}
In diesm Test lege ich einen bool “eventRaised” an - sobald das Event geworfen wird, wird ein anonymes delegat aufgerufen (man spart sich hier die zweite Methode) und ich setzt einfach diesen boolean auf “true”.
Sehr einfach und genial um zu testen, ob das Event wie gehofft auch geworfen wird
[Download Source Code]
ShareThis
DDD – Allgegenwärtige / Universelle Sprache
11.07.2008 20:49:00
|
Sebastian Jancke
Die zentrale Technik im Domain Driven Design ist die “Allgegenwärtige / Universelle Sprache”, engl. ubiquitous language. Um zu erklären, wobei es darum geht, möchte ich zunächst weg von der technischen Sprache. Wir alle kennen das Phänomen, das manche Filme und Bücher in der originalen Sprache einfach aussagekräftiger und präziser sind. Übersetzungen gehen oft soweit, das Film- und Buch-Titel einfach die Aussage des originalen Titels kaum noch widerspiegeln. Übersetzungen erzeugen schlicht kleine Fehler und Abweichungen, die in der Summe (bei schlechten Übersetzungen) einfach nicht mehr die selbe Aussage treffen.
Übertragen wir diese Film-Metapher auf die Software Entwicklung. In manchen starren Prozessen gibt es Entwickler, Architekten und Analysten. Letztere kommunizieren direkt mit dem Kunden, übersetzten die Anforderungen des Kunden in die Sprache der Architekten und Entwickler. Meist haben Entwickler und Architekten kaum noch die selbe Sicht und Sprache auf die Domäne wie der Kunde. Wie wir bereits gesehen haben, sind solche Übersetzungen nicht trivial und fehlerfrei. Im Extremfall kommunizieren die Team-Mitglieder in einer anderen Sprache und damit mit einem anderen Modell, als im Quelltext des Systems abgebildet ist. Sicherlich kennt fast jeder solche Projekte und Situationen.
Evans schlägt hier einen Ansatz vor, der zunächst manchem radikal erscheinen mag. Aber wie bei allen neuen Techniken ist ein wenig Dogmatik der Verbreitung sicherlich dienlich (siehe zum Beispiel TDD). Evans schlägt eine vereinheitlichte Sprache zwischen Kunden und Entwicklern vor. Diese allgegenwärtige, universelle Sprache bildet das Modell der Domäne ab. Diese Sprache manifestiert sich in der Kommunikation zwischen Team und Kunden, in Dokumenten und natürlich auch im Quelltext. Sie ist das Kernstück, das Rückgrat des Modells und muss unbedingt von allen Mitgliedern des Team kommuniziert werden.
Interessant zu diskutieren wäre hierbei das gebräuchliche Vorgehen, Quelltext in englischer Sprache zu halten während wir fast alle auf Deutsch kommunizieren. Denn dies ist bei deutschsprachigen Kunden schließlich auch eine Übersetzung. Auf der anderen Seite gehört es zum “guten Ton”, Quelltext auf Englisch zu schreiben – wohl auch weil die deutsche Sprache teilweise einfach mehr Wörter zum Ausdruck braucht.
Die Wahl der Dokumentation ist unbedingt dem Kontext anzupassen, denn die allgegenwärtige Sprache wird sich über einige Zeit weiterentwickeln und ständig Verändern, bis sie sich in ihrem Kern stabilisiert hat. Für manche Projekte machen hier sicher starre UML-Diagramme und Anforderungsdokumente Sinn, wenn das Projekt nicht mit dauernden Änderungen in der Domäne umgehen muss. Für Domänen mit häufigen, ständigen Änderungen empfehlen sich dann wohl eher temporäre Diagramme (Skizzen). Evans gibt hierbei zu bedenken, das gerade Diagramme eher nur Ausschnitte des Modells reflektieren sollten. Gerade UML-Diagramme tendieren dazu, sich extrem zu vermehren oder aber allumfassend zu werden. Solche Diagramme sind sicherlich kaum zur Kommunikation geeignet, weil es viel zu lange dauert sie zu lesen und zu ändern. Gerade in der Anfangsphase der Modellierung ist das Modell in der Regel noch nicht Stabil genug. Hier sind Skizzen und Ausschnitte unter Umständen viel schneller von Hand zu erstellen als jedes andere Diagramm. Zu bedenken ist auch, dass je mehr komplexe Dokumentation des Modells es gibt, desto mehr Artefakte müssen auch synchron mit der Entwicklung der Sprache gehalten werden. Aus der eigenen Erfahrung kann ich berichten, dass in manchen Projekten einfache digitale Mindmaps ausreichen, um Änderungen an der Sprache (und damit auch an der Domäne) zu dokumentieren. Dies hat unter Umständen aber den Nachteil, dass alle Dokumente fast nur inkrementelle Ausschnitte sind. Auf der anderen Seite sind solche Mindmaps schnell geschrieben, strukturiert und auch schnell verstanden.
Egal welcher Ansatz gewählt wurde: Die Sprache und damit auch das Modell der Domäne manifestiert sich vor allem in der Kommunikation zwischen Team und Kunde und vor allem auch innerhalb des Teams. Dies bedeutet automatisch, dass jedes Konzept in der Sprache seine Entsprechung im Quelltext finden muss. Alle Änderungen in der Sprache sind somit auch eine Änderung am Modell. Somit muss dann auch der Quelltext die Änderungen mittragen. Andersherum kann es auch Konzepte geben, die bisher nur implizit vorhanden – und vielleicht bisher kaum verstanden - sind. Dies manifestiert sich vielleicht erst in der Entwicklung des Systems. Solche impliziten Konzepte müssen dann explizit gemacht werden und auch Eingang in die Sprache finden. Erst dann sind sie schließlich mit den Experten in der Domäne diskutierbar.
Unsere allgegenwärtige, universelle Sprache ist während der Modellierung natürlich Änderungen unterworfen - auch wenn wir Big-Design-Up-Front arbeiten. Dann gibt es diese Änderungen hoffentlich nur während der Modellierungs-Phase (was ich aus meiner bisherigen Erfahrung eher selten glaube). Dies kann dazu führen, dass dass Modell der Domäne nicht mehr mit den neuen Anforderungen skaliert. Dann ist es Zeit, Teile des Modells zu entfernen und neu zu beginnen. Dies erreicht man im Prinzip nur durch Experimenten mit dem Modell und Diskussion mit den Experten der Domäne. Beispiele für solche Vorgänge sind in den Büchern von Eric Evans und Jimmy Nilsson in vielen Beispielen zu finden.
Einen Vergleich, den ich sehr interessant finde, gibt es zwischen der allgegenwärtigen Sprache des DDD und der System-Metapher des eXtreme Programming. Die Metapher wurde in den ursprünglichen XP-Büchern kaum behandelt (nur auf wenigen Seiten) und ist daher vielleicht eine der kaum praktizierten Methoden (jedenfalls hört und liest man eher selten davon). Man kann die allgegenwärtige Sprache im Domain Driven Design als ein Schlüssel zur Erstellung einer solchen System-Metapher auffassen.
Abschließend ein Ausblick:
In den folgenden Teilen der Serie stelle ich den von Evans eingeführten Sprach-Elemente vor, um aus der Kommunikation mit Experten der Domäne dann ein Modell zu strukturieren. Weitere Teile werden Techniken enthalten um Konzepte im Quelltext expliziter zu machen, das Modell reichhaltiger zu gestalten, seine Anstrengungen in der Modellierung zu fokussieren und ein Modell gegen unerwünschte Einflüsse zu schützen sowie mit anderen Modellen zu integrieren.
Zum Schluss: ein gutes Beispiel für schwierige Übersetzungen ist schon der Titel dieses Beitrags: “ubiquitous langauge” lässt sich übersetzten, verliert dann aber schnell an Bedeutung – jedenfalls in meinem sprachlichem Empfinden.
DDD Serie:
DDD – Eine Einführung
11.07.2008 20:18:00
|
Sebastian Jancke
Dies ist der Beginn einer Serie zum Thema Domain Driven Design (DDD). Ich werde versuchen, die Serie mit weiteren Beiträgen zu füllen, während ich an einem Vortrag zum Thema arbeite.
Domain Driven Design ist der Titel eines Buches von Eric Evans. Das Buch selbst ist nicht mehr “ganz neu” – trotzdem gab es (gerade auch in den Java und ALT.NET Gemeinschaften) einigen “Hype” darum. Dabei gibt es eigentlich nicht genau das DDD. Zunächst ist DDD eine Kombination aus “Model Driven Design” (nicht zu verwechseln mit MDA – Model Driven Architecture) und Prozessen rund um Kunden-Kommunikation, Integration und Modularisierung verschiedener Domänen, etc..
Nun stellt sich natürlich die Frage, wann DDD eingesetzt werden sollte. Zunächst ist die Ansatz der allgegenwärtigen Sprache sicherlich universell nutzbar. Um den vollen Nutzen zu erfahren ist aber sicherlich die Kopplung mit Model Driven Design empfehlenswert. Ausgangspunkt ist das Domain Model Pattern von Martin Fowler. Hierbei sollen die kompletten Fähigkeiten unserer OOP-Sprachen genutzt werden, um die Komplexität der Domäne in ein reichhaltiges Objektmodell zu übersetzten.
Damit dürfte klar sein, dass Model Driven Design, also OO-Modelle der Domäne, sich vor allem in OLTP-Szenarios einsetzten lassen. Derzeit ist Objekt-Orientierung einer der stärksten Ansätze, die wir kennen, um hohe Modularisierung und Reduzierung der Komplexität zu erreichen.
Dies bedeutet aber auch, dass OLAP-Szenarios ungeeignet für den Einsatz eines reichhaltigen Objektmodells sind – denn hierbei geht es primär um die Analyse von Daten und weniger um komplexe Prozesse und Regeln innerhalb von Transaktionen. Die Frage nach Ad-hoc Reporting wurde auf der DDD-Mailingliste schon mehrfach gestellt und auch beantwortet – mittlerweile gibt es dazu auch einen Artikel und eine Diskussions-Zusammenfassung auf der Webseite zu DDD. Ich möchte hier nicht direkt im Detail darauf eingehen - dies werde ich zu einem späteren Zeitpunkt noch tun. Zusammenfassend möchte ich aber sagen, das solche Objektmodelle und damit verbundene Datenbank-Schemata eigentlich nicht zum performanten Ad-hoc Reporting geeignet sind. Alternativen wären flache, spezielle Datenbank-Sichten oder eine Art Data-Warehouse-Lösung.
Ziel des Model Driven Design ist also die Erstellung eines reichhaltigen Objektmodells, das wir mit völliger Freiheit unter Ausnutzung aller Sprach-Features erstellen. Zu starke Kopplung an die Infrastruktur (etwa Basisklassen mit Transaktions- und Datenbank-Logik) sind hier hinderlich. Deshalb ist der Begriff “Persistence Ignorance” eng mit der Erstellung solcher Objektmodelle verbunden. Objekte ohne Einschränkung durch Infrastruktur oder Runtime-Container (wie zB EJB 2.0 - Java , CSLA - dotnet) werden auch POCOs genannt – Plain Old CLR Objects. Ich würde sogar noch ein Stück weiter gehen und von Infrastruktur-Ignoranz sprechen, da Persistenz nur ein Aspekt der Infrastruktur ist.
Natürlich bedeutet dies auch, dass wir gewisse Anforderungen an die Architektur solcher Systeme haben, auf der anderen Seite aber auch eine Menge gewinnen. Zunächst möchte ich auf die Gewinne eingehen. Solche Objektmodelle eigenen sich perfekt um Komplexität zu verstecken und Änderung zu handhaben. Die Arbeit mit solchen Modellen führt zu ausdrucksstarken Kombinationen aus bereits bestehenden ausdrucksstarken Teilen. Damit gewinnen wir als Lesbarkeit und auch Wartbarkeit. Objektmodelle ohne Abhängigkeit von der Infrastruktur bedeuten natürlich auch einfache Testbarkeit. Somit ist es möglich, die gesamte komplexe Logik in Objektmodelle mit automatisierten Tests ständig zu überprüfen. Unsere Architektur wird hier also testbar und wir werden nicht gehindert Tests zu entwickeln oder gar Test-first (TDD) zu arbeiten. Solche Objektmodelle erleichtern die Arbeit mit automatisierten Tests geradezu.
Es gibt aber auch einige Anforderungen an unsere Architekturen, um solche reichhaltigen Objektmodelle im Sinne von DDD zu entwickeln. Diese sind nicht unbedingt negativ (haben sogar positiven Einfluss auf das gesamte System), sind teilweise aber fast Voraussetzung. Zunächst einmal lassen sich Infrastruktur-Ignorante Objektmodelle im Prinzip nur entwickeln, wenn wir das Dependency Inversion Principle anwenden. Dies führt sofort zur Notwendigkeit eines Dependency-Injection-Containers zur Konfiguration der Abhängigkeiten. Natürlich ließe sich das DIP auch anders realisieren, pragmatisch gesehen nehmen uns solche Container (Castle Windsor, Spring.NET, StructureMap, Ninject) aber sehr viel Arbeit ab. Der Einsatz eines solchen IoC/DI-Containers hat darüber hinaus einen positiven Effekt auf die gesamte Anwendung: lose Kopplung ist schließlich eine Eigenschaft, die man sich generell (neben hoher Kohäsion) für seine Module wünscht.
Soll unser Objektmodell persistiert werden, brauchen wir auch einen starken, flexiblen O/R-Mapper. In letzter Zeit gab es viel Rumoren um das Entity Framework – um es kurz zu machen: derzeit sieht sowohl das EF-Team als auch die ALT.NET Community das EF nicht als eine Option an, wenn man DDD nutzt. Derzeit stärkstes Framework auf dem Markt ist damit NHibernate, eine ehemalige Portierung des Java-Frameworks Hibernate. NHibernate ist komplett in das dotnet-Ökosystem integriert und weit verbreitet.
Die Entwicklung reichhaltiger Objektmodelle erfordert zudem generell eine Leistungsfähige Entwickler-Struktur, denn ohne gute, OOP-erfahrene Entwickler ist es schwierig ein reichhaltiges Objektmodell zu erstellen. Viele Entwickler sehen sich zwar als OOP-erfahren, doch ist es noch ein großer Unterschied eine OOP-Sprache wie C# einzusetzen oder aber gute, leistungsfähige Objektmodelle zu entwickeln. Dies merkt eigentlich jeder, der bisher noch nie mit aller Kraft versucht hat, ein Modell noch reichhaltiger zu machen. Die ersten Versuche scheitern wohl in der Regel (und sind es auch bei mir). Das eingestehen und sehen der Fehler ist hier der Schlüssel um sich weiterzuentwickeln. Viele open-source Projekte nutzen reichhaltige Objektmodelle und sind optimal zum Lernen.
Zum Schluss noch einige Worte zum Aufwand. Ein Objektmodell zur Repräsentation der Domäne zu erstellen ist harte Arbeit und aufwendig – dem gegenüber steht aber die Leichtigkeit mit der später Komplexität gehandhabt werden kann (wenn das Modell gut genug ist). Als Alternativen gäbe es noch die Arbeit mit Transaction Scripts (Fowler) oder dem Table Module Pattern (Fowler). Letzteres erfreut sich gerade bei .NET-Entwicklern großer Beliebtheit wegen der guten IDE-Unterstützung in Visual Studio, dort bekannt als DataSets. Für einen genauen Vergleich möchte ich auf Martin Fowler’s Buch “Patterns of Enterprise Application Architecture” verweisen. Es sei aber gesagt, dass gerade das Table Module Pattern wahrscheinlich am wenigsten mit steigender Komplexität der Domäne skaliert, gefolgt von Transaction Scripts. Beide haben vor allem das Problem von Copy&Paste-Smells und Dopplung von Logik. Nach derzeitigem Stand ist ein objekt-orientieres Modell das einzige, das wirklich besser mit wachsender Komplexität skaliert. Problematisch ist allerdings, dass man Komplexität schlecht messen kann und keiner genau weiß, wo die Schwellwerte liegen. Letzten Endes ist hier die Erfahrung und Experimente / Prototypen gefragt.
Ein Bild sagt mehr als 1000 Worte - Screenshots an Workitems anhängen
10.07.2008 19:33:24
|
Thomas Schissler
Jeder kenn die Situation. Wenn man einen Fehler oder eine Änderung beschreiben will,
tipp man sich einen Wolf. Viel schneller geht es mit einem Screenshot. Wer allerdings,
z.B. beim Testen zig Screenshots an Workitems im Team Foundation Server anhängen möchte,
der ist auch schnell genervt. Immer der gleiche Prozess. Schreenshot aufnehmen - In
das Bildverarbeitungsprogramm wechseln - Screenshot einfügen - Screenshot speichern
- Neues Workitem anlegen udn Felder ausfüllen - Attach File aufrufen - Datei mit Screenshot
suchen - Fertig!
Glücklich derjenige, der den artiso
Workitem Manger nutzt. Da geht das Ganze viel einfacher.
1.) Das Tray Icon Symbol mit der rechten Maustaste anklicken und auswählen ob man
ein neues Workitem anlegen möchte oder an das gerade geöffnete den Screenshot anhängen
möchte.
2.) Geünschten Bildbereich auswählen und Screenshot aufnehmen (Klick auf den Button
im Zentrum des Fensters oder Enter drücken)
3.) Schon ist das Workitem inkl. Attachment angelegt.
Wer das Ganze mal testen möchte, kann sich hier eine
Demo-Version des Workitem Managers herunterladen.
Das Eis taut für Open XML
10.07.2008 13:53:58
|
Jens Häupel
Vor einiger Zeit gab es wieder etwas Wirbel um die Standardisierung von Open XML, legte doch die ISO den Prozeß nach Einsprüchen einiger Miglieder vorerst auf Eis.
Die Gründe für die Einsprüche von Indien, Südafrika, Venezuela und Brasilien gegen die Zertifizierung von Open XML konnten von den Generalsekretären der ISO bzw. IEC allerdings nicht bestätigt werden. Sie haben keine Regelverstöße im Zertifizierungsprozeß ausmachen können. Somit wird der Standard DIS 29500 voraussichtlich ratifiziert und dann natürlich auch veröffentlicht.
Ein weiterer Aufschrei machte vor kurzem ebenfalls die Runde: Microsoft's amerikanischer National Technology Officer, Stuart McKee, wurde mit der Aussage zitiert "ODF habe den Wettkampf der Standards klar gewonnen..."
O-Text:
"ODF has clearly won," said Stuart McKee, referring to Microsoft's recent announcement that it would begin natively supporting ODF in Office next year and join the technical committee overseeing the next version of the format.
Interessanterweise wurde bei der Zitierung einer Nachricht von der anderen die eigentliche Aussage immer ein bißchen weiter verändert. Stuart McKee's Aussage zielte darauf, daß ODF nun klar an Verbreitung gewinnt, da mit der Bereitstellung entsprechender Konverter im nächsten Office 2007 Service Pack Anfang 2009 dann Millionen Microsoft Office Nutzer auch ODF zur Verfügung steht.
Microsoft wird natürlich nach wie vor Open XML als das native Format von Office 2007 einsetzen und ODF gleichzeitig als Alternative anbieten auch für Fälle, wo das Format der Schlüsselfaktor für den Einsatz eines bestimmten Office Paketes wird. ODF ist allerdings nach wie vor nicht in der Lage, alle Funktionalität eines Microsoft Office Dokuments abzubilden. Das ist aber eine Begrenzung der derzeitigen Implementation und mag in einer der nächsten Versionen anders sein. Auch Standards unterliegen Änderungen, durch die sie gewanchsenen Anforderungen angepaßt werden.
HowTo: 3-Tier / 3-Schichten Architektur
09.07.2008 22:38:20
|
Robert Mühsig
Eine 3-schichtige Architektur ist eigentlich ein “Klassiker” in der Softwareentwicklung. Da allerdings das Thema sehr weitläufig ist und Anfänger (und unbelehrbare Entwickler) aus Mangel an Zeit, Lust oder Erfahrung zurückschrecken gibt es genügend Beispiele wo einfach darauf verzichtet wurde.
Um den Grundgedanken zu vermitteln und um zu zeigen, dass es eigentlich sehr einfach ist, sowas am Projektanfang zu implementieren, schreibe ich diesen Artikel
Was für “Schichten” und warum 3?
Fast jede Software greift auf Daten zurück - sei es XML, ein Webservice, eine Datenbank, eine Textdatei oder ein X-beliebiges anderes System.
Diese Daten werden irgendwie verarbeitet - sei es eine mathematische Funktion, eine Validierung oder eine bestimmte Filter und Suchfunktion.
Damit das Ergebnis auch irgendwo angezeigt wird (bzw. die Eingaben entgegen genommen werden) gibt es in den meisten Fällen auch ein Frontend, sei es eine Consolen-Applikation, eine Website oder irgend etwas anderes.
Jetzt wären wir bei den 3-Schichten angekommen:
“Nur 3? Ich hab mehr!”
Natürlich kann man unzählige Schichten noch dazwischen schieben. Ein Beispiel ist z.B. die Software Factory von Microsoft. Da gibt es noch etliche Mappings zwischen den Data-Access-Schichten bis hin zu den Service-Schichten. Siehe auch den Wiki-Artikel zu den Schichtenmodellen.
“Ich frage nur Daten ab - ich brauch nur 2 Schichten.”
Über SQL etc. kann man natürlich auch Filtern, Sortieren etc. - da könnte man auch die “Business” Schicht in Frage stellen. Aus meiner Erfahrung sollte man das allerdings lieber nicht machen - später können noch irgendwelche Anforderung dazukommen, die nix im Data Access Layer zu suchen haben. Bis es soweit ist, könnte die Business-Schicht die Daten einfach nur “durchreichen”.
Beispielapplikation:
Data-Access Layer: “ThreeTier.Data”
Business Layer: “ThreeTier.Service”
Presentation Layer: “ThreeTier.ConsoleApp”
+ Unit-Tests: “ThreeTier.Tests”
Hier haben wir eine recht einfache Beispielapplikation - das ganze noch mit ein paar kleinen Unit-Tests bestückt (Einführung in Unit-Tests hier).
Die Architektur sieht man z.B. auch recht gut in Rob Conerys Storefront Projekt.
Schichten im Detail: ThreeTier.Data
Hier definiere ich erst mal meine Objekte, welche ich im System nutze - simple POCOs.
Zugegeben, man kann sich darüber streiten ob man sein “Model” tatsächlich mit in dem Data-Projekt haben möchte. Da allerdings alles meistens mit irgendwelchen Daten zusammenhängt, passt das schon.
Wir haben hier nur die User Klasse:
Im Ordern “DataAccess” liegt unsere Schnittstellen (Einführung zu Schnittstellen) zu den Datenquellen.
In diesem Fall haben wir nur die Schnittstelle “IUserRepository” (Repository zu dt. sowas wie Lager, Speicherort etc.) - dort definieren wir, welche Operationen ich generell auf eine X-beliebige Datenquelle ich ausführen möchte:
Das “DemoUserRepository” ist die konkrete Implementierung dieser Schnittstelle. Da ich keine DB oder ähnliches wollte, werden hier statische Daten zurückgegeben.
Welchen Vorteil bringt mir jetzt das Interface?
Das Interface könnte man hier in Frage stellen, allerdings erlaubt es später recht einfach die Datenquelle zu wechseln - weil alles auf der Schnittstelle beruht.
Da man im Regelfall mit einer DB etc. arbeitet möchte man z.B. in Unit-Tests nicht unbedingt die Datenbank fluten, sondern kann sich hier statische Testdaten zurückgegeben lassen. Einfach durch die Schnittstelle.
So könnte man auch leichter von einem “Showcase” zu einer echten Implementierung umschwenken.
Da ich in meinem Beispiel aber nur statische Daten zurückgebe, habe ich im Unit-Tests genau diese getestet.
Schichten im Detail: ThreeTier.Service
Im Service haben wir nach dem gleichen Prinzip auch eine Schnittstelle für unseren “UserService” erstellt.

In unserem UserService gibt es einmal eine Login-Methode und eine Methode, welche (ganz im Sinne von Social Networking) die Freunde von einem User zurück gibt. Hierbei habe ich zudem auch nur statische Daten genommen. Das ganze basiert allerdings auf dem “UserRepository”.
Schichten im Detail: ThreeTier.ConsoleApp
Mal wieder ein Konsolenprogramm - zwar ist das keine schöne Oberfläche, aber für das Beispiel soll es genügen:
static void Main(string[] args)
{
Console.WriteLine("Great Social Community System - Please Login...");
Console.Write("Name: ");
string loginname = Console.ReadLine();
Console.Write("PW: ");
string password = Console.ReadLine();
IUserService srv = new DemoUserService();
if (srv.Login(loginname, password))
{
Console.WriteLine("Hello: " + loginname);
Console.WriteLine("Your demo friend collection in the system: ");
List<User> friends = srv.GetFriendsFromUser(loginname).ToList();
foreach (User friend in friends)
{
Console.WriteLine(" + " + friend.Login + " - Id: " + friend.Id);
}
}
else
{
Console.WriteLine("Login failed");
}
Console.ReadLine();
}
Ausgabe:
Extras: Unit-Tests
Um ein gutes Beispiel zu geben, habe ich sogar 6 Unit-Tests geschrieben. Das Frontend hab ich allerdings nicht getestet
Code-Coverage: 97% (Data + Service)
Resultat:
Durch die 3-Schichtige Architektur ist es später leichter Möglich neue Features einzubauen und die Applikation zu Warten. Im Team macht sich das auch recht gut, da man dadurch eine bessere Teamaufteilung machen kann.
[ Download Source Code ]
ShareThis
WPF: DataSet und Data Binding
09.07.2008 18:55:28
|
Norbert Eder
Gerade zum Thema Data Binding habe ich mittlerweile einige Beispiele erstellt (siehe
hier,
hier und
hier). Was noch fehlt (und oft gefragt wird) ist, wie ein DataSet gebunden werden kann. Dem möchte sich dieser Artikel widmen.
Auch in diesem Fall ändert sich relativ wenig. Anstatt eines konkreten Objektes steht nun ein DataSet zur Verfügung. Dieses kann Tabellen enthalten (0 - n) und jede Tabelle kann Spalten enthalten. Das Grundprinzip besteht nun einfach darin:
Vorgehensweise anhand einer TreeView
Das DataSet wird als
DataContext bei der TreeView gesetzt. Das Root-Element wird beispielsweise per Hand (Markup) angelegt. Diesem wird nun eine Tabelle des DataSets als ItemsSource gesetzt. In weiterer Folge wird ein
HierarchicalDataTemplate erstellt, welches nun dafür zuständig ist, die einzelnen Felder (oder auch nur ein paar wenige davon) darzustellen.
In weiterer Folge kann nun eine Eingabemaske erstellt werden, welche an das
SelectedItem der TreeView gebunden ist und den ausgewählten Datensatz zur Anzeige bringt und somit bearbeitbar macht.
Konkretes Beispiel
Ob nun das DataSet aus einer Datenbank geladen oder manuell erstellt wird, macht keinen Unterschied. In diesem Beispiel wird ein DataSet per Code generiert und zur Verfügung gestellt:
public static class DataSetMock
{
public static DataSet CreateDataSet()
{
DataSet ds = new DataSet();
ds.Tables.Add("Person");
ds.Tables[0].Columns.Add("FirstName");
ds.Tables[0].Columns.Add("LastName");
ds.Tables[0].Rows.Add("Norbert", "Eder");
ds.Tables[0].Rows.Add("Hugo", "Tester");
return ds;
}
}
Dies geschieht in diesem Fall durch eine statische Klasse. Die Methode
CreateDataSet kann nun über einen
ObjectDataProvider via XAML zur Verfügung gestellt werden. Damit beschränken wir uns beim Schreiben von Sourcecode auf die Implementierung eben dieser statischen Klasse.
Wie bereits angesprochen, wird das DataSet in diesem Beispiel über ObjectDataProvider zur Verfügung gestellt:
<ObjectDataProvider
x:Key="PeopleProvider"
MethodName="CreateDataSet"
ObjectType="{x:Type local:DataSetMock}"/>
Der Provider selbst muss in den entsprechenden Ressourcen (in diesem Fall in den Ressourcen des Fensters) definiert werden.
In weiterer Folge wird nun ein TreeView-Element auf dem Fenster positioniert. Als
DataContext wird der zuvor erstellte Provider gesetzt, da dieser die generierten Daten (DataSet) zurück liefert:
<TreeView
DataContext="{StaticResource PeopleProvider}"
x:Name="PeopleTreeView"
DockPanel.Dock="Left"
Width="200">
<TreeViewItem
x:Name="PeopleRoot"
Header="People"
ItemsSource="{Binding Person}"
ItemTemplate="{StaticResource PersonTemplate}"/>
</TreeView>
Im Source ist zusätzlich zu sehen, dass eine Root-Node über das Markup erstellt wird. Diesem Root-Node wird als
ItemsSource nun per Binding die Tabelle
Person aus dem DataSet zugewiesen. Dadurch wird der Gültigkeitsbereich des Bindings für diese Node festgelegt. Somit kann nun ein
ItemTemplate definiert werden, welches für die Darstellung der einzelnen Datensätze zuständig ist:
<HierarchicalDataTemplate x:Key="PersonTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding LastName}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding FirstName}"/>
</StackPanel>
</HierarchicalDataTemplate>
Dieses Template wird ebenfalls in den Ressourcen des Fensters abgelegt. Zusätzlich muss es der Root-Node als
ItemTemplate zugewiesen werden.
Das wären bezüglich TreeView alle notwendigen Schritte. Möchte man nun ein Eingabeformular erstellen, welches die Daten des aktuell selektierten Datensatzes anzeigen soll, dann kann nun folgendes definiert werden:
<StackPanel Orientation="Vertical">
<TextBlock Text="Firstname"/>
<TextBox
Text="{Binding
ElementName=PeopleTreeView,
Path=SelectedItem.FirstName}"/>
<TextBlock Text="Lastname"/>
<TextBox
Text="{Binding
ElementName=PeopleTreeView,
Path=SelectedItem.LastName}"/>
</StackPanel>
Was passiert hier? Es wird ein StackPanel definiert, welches alle Kind-Elemente untereinander anordnet. Die Elemente vom Typ
TextBlock stellen die Überschriften dar. Für die eigentlichen Daten wird jeweils eine
TextBox verwendet. Diese enthalten ein Binding auf die jeweilige Spalte des aktuell selektierten Datensatzes. Änderungen schlagen sich natürlich sofort auf die TreeView durch.
Fazit
Das Data Binding funktioniert grundsätzlich immer gleich. Wichtig ist, dass bewußt ist, wann welcher Kontext gesetzt wird und was dieser Kontext tatsächlich zur Verfügung stellt. Darauf kann gebunden werden. Dies verhält sich bei einem DataSet gleich wie bei einer Objekthierarchie.
Das gezeigte Beispiel steht natürlich als
Download zur Verfügung.
Things to note when upgrading to BlogEngine.NET 1.4
09.07.2008 12:07:08
|
Andre Loker
Several days ago I upgraded the blog software (BlogEngine.NET) from version 1.3.1 to 1.4. Here are some changes that sneaked in to the new version:
- The RSS feed generator creates a different value for the <author> element. In version 1.3.1 it was simply the blog owner's name (here: Andre Loker), in version 1.4 the name is composed of a "anti-spammified" version of the blog owner's email address and his name (here: mail.nospam@nospam.andreloker.de <Andre Loker>). I don't like it, so I reverted it to the original behaviour:
- Locate BlogEngine.Core\SyndicationGenerator.cs (in the source package)
- Comment out line 554 (prefixed by "Was" in the picture)
- Add the line prefixed by "Now" in the picture
- When you receive a mail that was sent via the contact form, the "From" field is filled with the e-mail address and name of the blog owner, a "Sender" field is added that contains the mail author's name and e-mail address. In my mail client, all mails coming from the contact form now have "Andre Loker" in the sender column. Again, I reverted this to the original behaviour:
- Locate contact.aspx.cs
- Comment out the line prefixed by "Was" in the picture
- Add the line prefixed by "Became" in the picture
- To be continued...
Note: Remember to apply the Memory leak fix for version 1.3, 1.3.1 and 1.4
Techno trouble
09.07.2008 10:43:19
|
Andre Loker
I encountered some technical problems yesterday and today. Sorry for that!
- The SMTP server has been unavailable since today 3 o'clock
- The RSS feed has been unavailable since I updated to version 1.4 of BlogEngine.NET (I forgot to upload a file...)
So if you tried to send me an email in the last 24 hours, especially regarding ReSharper licenses, I ask you to send it again if I haven't answered yet.
By the way: if you are sending me a mail and expecting a reply, please make sure you provide a working(!) e-mail address. I have several requests for ReSharper evaluation license that I can't answer, simply because my response won't make it back to the sender.
Registrierungsformulare optimal gestalten
09.07.2008 08:34:24
|
Jürgen Gutsch
Registrierungsformulare optimal gestalten
09.07.2008 08:34:24
|
Jürgen Gutsch
The Web Standards Curriculum
09.07.2008 08:26:16
|
Jürgen Gutsch
The Web Standards Curriculum
09.07.2008 08:26:16
|
Jürgen Gutsch
Sandcastle Source released
09.07.2008 03:27:27
|
Rainer Schuster
Da ist mir doch letzte Woche glatt eine wichtige News durch die Lappen gegangen. Nach einer hitzigen Diskussion und dem Aufschrei, Microsoft würde auf seiner eigenen OpenSource-Plattform ein ClosedSource Projekt hosten wurde Sandcastle erst einmal entfernt, wie ich bereits berichtet habe.
Es wurde daraufhin diskutiert, was geschehen sollte.
- eine Relaunch auf Codeplex mit Sourcecode
- ab damit in die Codegallery (http://code.msdn.microsoft.com)
Da sich Microsoft ja mit diversen OpenSource Aktivitäten und milliardemschwerem Aufwand bemüht, das nachzuholen, was Jahre lang verschlafen wurde, war hier nur eine Option denkbar. das Projekt ist wieder unter der alten Adresse mit Sourcecode beziehbar.
Vielen Dank Microsoft und dem Manager Anand Raman. Wer sich darüber hinaus was Sandcastle angeht immer auf dem laufenden halten will, kann direkt auf der Forumseite vorbei schauen.
HowTo: Generische Extensions
08.07.2008 21:07:37
|
Robert Mühsig
C# 3.0 bringt ein nettes Feature mit: Extensions.
Generell sind die recht einfach, allerdings sind die meisten Beispiele ohne Generics gemacht.
Meine Problemsituation:
In einem Projekt waren einige Klasse von List<…> abgeleitet:
public class MyList : List<MyObject>
{
...
}
Jede dieser “Listenklassen” hatte eine kleine Methode, welche diese Liste durchgeht und eine Aktion auslöst.
Ganz nach dem Prinzip: Keep it DRY
Don´t repeat yourself - daher müssen diese eigentlich gleichen Methoden weg und in eine Extension Methode. Da der Syntax von den generischen Extensions mir etwas Zeit geraubt hat, stell ich ihn mal der Allgemeinheit offen.
In unserem Beispiel:
Wir wollen eine kleine Extension schreiben, welche mehrere Elemente an eine ICollection anfügt - kann man auch über AddRange lösen, aber z.B. hat die ObservableCollection das nicht - daher nehmen wir einfach mal dieses Beispiel.
Source Code:
Main:
class Program
{
static void Main(string[] args)
{
List<int> intList = new List<int>();
intList.Add(1);
intList.Add(2);
intList.Add(3);
List<int> newIntList = new List<int>();
newIntList.Add(4);
newIntList.Add(5);
intList.Add(newIntList);
foreach (int myInt in intList)
{
Console.WriteLine(myInt); // Should be 1,2,3,4,5
}
Console.ReadLine();
}
}
Extension:
public static class Extensions
{
public static ICollection<T> Add<T>(this ICollection<T> src, ICollection<T> addingElements)
{
foreach (T element in addingElements)
{
src.Add(element);
}
return src;
}
}
Resultat: Viele Ts und eine kleine generische Extension.
[ Download Source Code ]
ShareThis
Did you know that...
08.07.2008 19:24:57
|
Andre Loker
... you can annotate an attribute class with an attribute of that very type?
[ThisIsCool]
public class ThisIsCoolAttribute : System.Attribute {
}
Admittedly I did not.
Objekt-Modellierung: Nomen? Verben? Beides? Keins?
08.07.2008 18:33:00
|
Sebastian Jancke
Die allermeisten lernen wohl OOP – bis heute – in seiner einfachsten Form zunächst als “Finding the nouns”. Die Nomen finden – und dann daraus Objekte machen. Fehlen noch Aktionen, um daraus Methoden für ein Objekt zu erstellen – wie gut das es ja noch Verben gibt.
Vielen Entwicklern dürfte klar sein, dass diese Schema zu simpel ist und höchstens für die ersten OOP-Beispiele ausreicht. Fortgeschrittenere “Richtlinien” und Möglichkeiten zur Strukturierung bieten unter anderem der DDD-Prozess, und das Entity-Controller-Boundary Modell.
Trotzdem habe ich schon zu oft Entwickler gesehen, die krampfhaft versuchen, ein Objekt nach Nomen zu benennen, obwohl ein Verb viel aussagekräftiger wäre. Das Resultat sind die allerseits beliebten Konstrukte:
AnotherCarFinder.FindCar(myCarId);
Neben der Dopplung – die sich sehr holprig liest – ist das ganze auch kaum aussagekräftig. Deshalb wäre mein Ratschlag: Weg vom OO-Denken in Nomen, hin zu mehr Flexibilität und Kreativität.
Das obige Beispiel sieht so sicherlich viel lesbarer aus:
FindAnotherCar.By(myCarId)
Dies ist sicherlich noch nicht das non-plus-ultra und auch nicht das Ende der Möglichkeiten. Deshalb möchte ich alle Leser auffordern, mir ihre Beispiele und Ideen doch vielleicht zukommen zu lassen. Mich interessiert brennend, welche Techniken andere (unter anderem) nutzen, um die Lesbarkeit zu erhöhen.
DocProject - MAML Editor kurz vor dem Release
08.07.2008 14:14:37
|
Rainer Schuster
In einer E-Mail von Dave Saxton habe ich erfahren, das die Preview des Editors kurz vor dem Release steht. Er hat die Zero-Bug Grenze erreicht. Die Konvertierung von MAML nach XAML und umgekehrt ist fertig. Die Commandbuttons müssen noch mit den entsprechenden Funktionen verknüpft und einige Styles angepasst werden. Danach steht einem Preview-Release nichts mehr im Wege. Die E-Mail hat mich schon vergangene Woche erreicht. Er hatte mir mitgeteilt über das Wochenende daran zu arbeiten und vielleicht heute die Version frei zu geben. Wenn wir Glück haben bekomme ich gegen heute Nachmittag/Abend dann die Preview unter meine Finger um sie zu testen.
Ich bin ein (leid)geprüfter Experte
08.07.2008 13:33:00
|
Jens Peter Kleinau
Es gibt mal wieder eine neue Community. Diese nennt sich "experto.de" und irgendein freundlicher? Mensch hat mich dort als Experten für .Net Entwicklung empfohlen. Die Idee der Community ist es, irgendwelche Menschen mit übertriebenen Mitteilungsbedürfnis als Experten für irgendwas zu identifizieren und für das Portal arbeiten zu lassen. Diese Arbeit ähnelt dem Beantworten von Hotline...
[[ This is a content summary only. Visit my website for full links, other content, and more! ]]
Wahlkampf mit Silverlight mal etwas anders
08.07.2008 10:40:42
|
Oliver Scheer
NDepend: code metrics at your service
08.07.2008 10:07:14
|
Andre Loker
If you ever wrote code for a non-trivial project chances are that from time to time you stop an think: "I don't know, but I have the feeling that the code is not really clean/too complex/[insert adjective here that makes you feel bad about your code]". Chances are even that you did not had these thoughts - but your source code indeed was not really clean, too complex or what not. While the latter situation is certainly the worse of the two, both situation make clear that we need means to quantify the quality of our code. And how do we quantify things? By attaching numbers to it, of course. While a statement such as "80% of my code is crap, I think" is certainly a quantification (one which is not applicable in practice, I hope though), we are looking for a tool that can do the math for us and tell us everything we want to know about our code...
... and here comes NDepend!
NDepend is an incredibly versatile tool that can help us improving our code base. The tool analyses project assemblies and source code regarding a multitude of metrics. NDepend can create static reports containing the results in tabular and graphical form, but it also provides an interactive tool (Visual NDepend) which allows us to drill down into assemblies, namespaces and types in virtually every possible way.
First of all, let's realize why it is so useful to have a tool like NDepend at hand:
Improve Communication
Communication is extremely important if you are developing software in a team. One reason why there are catalogues of design patterns is the fact that they introduce a vocabulary that developers get used to. If I talk about abstract factories, commands and strategies, my colleagues know what I mean.
Using NDepend extends the developers' vocabulary and enriches the way in which developers can communicate. This can be a dialogue between two developers: A: "Hey, this type has high efferent coupling, we need to have a look at it" - B: "You're right, it also has a high lack of cohesion value" - A: "Looks like we should concentrate our next refactoring session on this type..." - B: "Absolutely!" - A: "... but first have a cup of coffee :-)" [note: the last statement is independent of any third party tools]
Track progression and evolution
NDepend is capable of comparing two builds of the same project. This allows us to quantify how the quality of a project evolves. For example, code refactoring should generaly lead to code that is less complex (for example in terms of "number of lines per method" or cyclomatic complexity). By comparing a build before refactoring with one after refactoring you can track how effective your refactoring session was.
Verify development guidelines
NDepend can help us enforcing guidelines that have been agreed upon. For example, you might define that method should not have more than X lines (or Y IL instructions) or that methods with more than 5 lines of code should have at least 20% comment. Checking those guidelines is easily done with NDepend.
Improve code quality
This is, of course, the ultimate goal of all of us - at least I hope :-) Having the numbers (ie. metrics) is one thing, taking consequences from those numbers is the other thing. The numbers (and graphs) NDepend gives us can help us spot places in the code that can be improved. Places which we might have overlooked otherwise. This gives us very concrete chances to improve our source code.
Some basic metrics
Before we go into detail on NDepend, here are some of the more advanced metrics that we will deal with:
- Afferent coupling (Ca)
This metric desribes the number of types or methods from outside of the current assembly that use a given type or method. The higher this value, the more important the given type or method is to users of the assembly. - Efferent coupling (Ce)
This is the counterpart of Ca: it describes the number of assembly external types/methods that a specific type/method uses. A high value indicates that the specific type/method is very dependent on the external assembly. - Relational cohesion (H)
A metric that describes how strong the types within a single assembly are related to each other. Generally, types within an assembly should be strongly related, but not too strong. - Instability (I)
This describes how sensitive an assembly is regarding changes ion assemblies it depends on. It is measured as the quotient of efferent coupling (Ce) and total coupling (Ca+Ce). - Abstractness (A)
Describes the ratio of abstract types in an assembly. - Distance from main sequence (D)
Instability and abstractness should be in a certain balance. With my own words, I would describe this balance like that: an assembly with high abstractness should be stable as it is most likely used as an input assembly for other assemblies. If it were instable, it would be likely that it has to change sooner or later and this change would ripple through all assembly that depend on this assembly. On the other hand, a very concrete assembly (low abstractness) is likely to be at the end of a dependency graph, that is, almost no assemblies depend on it. It can and will therefore be quite instable. - Lack of cohesion (LCOM)
In a coherent class, most of the methods will deal with most of the fields of that class. If you find that many methods in the class deal only with a subset of the fields it might be an indicator that the responsibility of the class is too broad and the class should be split. - Cyclomatic complexity (CC)
This metric describes how many pathes a method has. The control flow in a method branches at every conditional statement, loop and other statements. A method with a high CC is hard to maintain.
Visual NDepend
Now that we are convinced that metrics are a Good Thing™ let us have a look at what NDepend brings along.
The NDepend package comes with two programs: the console runner (NDepend.Console.exe) and the graphical user interface (Visual NDepend). The former will be mostly used in automatic builds. To get in touch with NDepend let's stick to the GUI.
User interface styles
Visual NDepend supports to styles:
- the "Menu & Toolbar" style - this is a look and feel comparable to MS Office 2003
- the "ribbon" style - this style uses the tabs & ribbons look and feel that you know from MS Office 2007
Here's what you get after you fire up VisualNDepend.exe. To the left, the "Menu & Toolbar" style, to the right the "ribbon" style:

As you see, both versions look very pleasing. The UI of Visual NDepend is extremely polished, certainly among the most polished UIs of any of the tools I use. Personally I prefer the ribbon style - it's well arranged and I can find everything quickly.
You can change between the two styles in the options:
Hint: to reduce the amount of place the ribbons take, double click on the tab header. The ribbons will then disappear:

A single click on a tab header will show the ribbon temporarily, a double click restores the view back to normal. This is useful if you need as much space as possible, e.g. when you're analysing a solution.
Creating a project - a simple example
Visual NDepend supports two operation modes which only differ in the fact whether you explicitly create a project file or not: if you just want to do a quick analysis, simply select the menu point "Select .NET assemblies". This will allow you to perform the analysis without creating an explicit project file.The other option is to create an explicit project. This is of course recommended if you need to perform the analysis more than one time (eg. in continuous builds). 
Let's just create a new project. You only need to name the project and enter a location for the project file (an xml file). I really appreciate the simplicity here. I don't like it if a program requires you to make a lot of decisions when creating a project.
After the new project is created you need to add assemblies that NDepend can analyse. To the left you have a list of "Application assemblies". Those assemblies are the ones that are compiled from the source code in the project. To the right there's a list of "Tier assemblies". These are the assemblies that your application assemblies reference, for example mscorlib, the System.* assemblies or other third party libraries. The separation between application assemblies and tier assemblies is extremely useful. Most likely you'll only want to analyse your own assemblies and their dependencies to the tier assemblies - there's no need to analyse the cohesion of classes in System.Core.dll.
To add application assemblies either drag and drop them from the Explorer to the application assemblies list or use "Add Assemblies of a Visual Studio solution" to use a .sln file to look up the project assemblies. The "View folders" button allows you to inspect and add folders from which application and tier assemblies should be loaded. After adding some application assemblies, my screen looks like this:
You can use the tabs on the left side to edit additional properties of your project, for example if and how to compare your project to an earlier build, where to put the report files and what to show in your report.
After we have set up the project, we're ready to go: it's time to run the analysis! NDepend will start analysing your assemblies and generating the report files. The generation process does not take to long, about 20 seconds for a medium sized project on a decent machine.
Result windows
This what you will see after running the analysis;
Class browser
On the left side you have a class browser which shows the assemblies, namespaces, types and members of your project. Application assemblies are black, tier assemblies are blue. If you hover over a type or member it is selected in the metrics window and the Info window displays the metrics of the selected element (see the description of the metrics window).
Metrics
The metrics window visualizes the relative and absolute size of assemblies, namespaces, types and methods in terms of lines of code, number of IL instructions etc. This allows us to easily pinpoint the most important types etc. at a glance. If you hover with the mouse over one of the squares it is highlighted, the metric value is shown and the Info window in the bottom left displays all metrics for the selected square:
There is a little issue with the Metrics window, though. While hovering or clicking a square updates the Info window, the selection is not fixed. This means that as soon as the mouse leaves the selected square, the Info window will be either empty (if the mouse does not hover a square) or reflects the element that is currently under the mouse. It would be better if a single click on an element in the Metrics window would fix the selection. Clicking on an element in the Class Browser by the way does fix the selection.
Double clicking a member will launch Visual Studio and open the appropriate file. Cool!
Info
The Info window shows metrics for the selected element in the Metrics window or in the Class Browser: number of IL instructions, number of lines, number of lines with comment, percentage of comments, cyclomatic complexity etc.
Dependencies
This window displays the dependencies between the assemblies and types in our project. Starting on the Assembly level this tool allows us to drill down to deeper levels (namespaces, types, members) to detect dependencies between on these levels. Again, NDepend displays application assemblies and tier assemblies differently.
Application assemblies are shown in a triangulated matrix: all app assemblies can potentially be used by other assemblies and use other assemblies at the same time. For example: in this test project, 6 methods in the assembly Vanilla.Web.Monorail together use 17 members of the assembly Vanilla.Web.
For tier assemblies only one direction is displayed, that is how they are used by app assemblies. For example, we can see that Vanilla.Web.MonoRail uses 68 types of the Castle.MonoRail.Framework assembly:
A single click on any of the squares is meant to show you a dependency graph. As of now this does not work on 64bit platforms. This issue comes from an incompatibility of the used graph rendering library with 64 bit systems. Patrick Smacchia promised that this problem will be taken care of in one of the next versions.
Graph creation works nicely on 32 bit platforms, though. I will show an example of this when I come to automatic builds.
By clicking on one of the "+" buttons on the left side or the top side of the matrix you can dig down to lower levels: namespaces, types, members. This gives you endless possibilities of determining dependencies.
If you want to focus on a specific dependency, double click the corresponding square and Visual NDepend will "zoom in" into this dependency:
Let's leave the Dependencies window alone for now - it's possibilities are countless, just play with it!
CQL Queries
Let me put it straight: this feature is just awesome! NDepend spits out a lot of metrics on its own, but it also gives you a powerful query language that you can use to gather almost any information about your source code that you like.
CQL (Code Query Language) is a query language similar to SQL - which is the first cool thing as most of us are used to SQL. Using CQL you can query against a large set of metrics. Have a look at the CQL specifications to see how complex the query language is.
To give you an example of a simple CQL query:
SELECT TYPES WHERE NbFields > 6
This query returns all types with more than 6 fields. Easy, hm? Another example: methods that are potentially unused:
SELECT TOP 10 METHODS WHERE
MethodCa == 0 AND // Ca=0 -> No Afferent Coupling -> The method is not used in the context of this application.
!IsPublic AND // Public methods might be used by client applications of your assemblies.
!IsEntryPoint AND // Main() method is not used by-design.
!IsExplicitInterfaceImpl AND // The IL code never explicitely calls explicit interface methods implementation.
!IsClassConstructor AND // The IL code never explicitely calls class constructors.
!IsFinalizer // The IL code never explicitely calls finalizers.
Taking the queries a step further, you can define constraints using CQL which can be used to express design guidelines or rules. For example if your design rule is to not have methods with more than 20 lines of code, you can express this constraint like this:
WARN IF Count > 0 IN SELECT METHODS WHERE NbLinesOfCode > 20
When you analyse your project NDepend will generate a warning for all methods that have more than 20 lines of code (you might want to refactor those methods).
In the CQL window you can group the queries. NDepend comes with a standard set of useful queries so you don't have to write everything from scratch.
The CQL query editor is - like the rest of the application - well polished. It provides syntax highlighting and code completion:
By the way, the CQL is constantly extended. New metrics are added in almost every new version of NDepend.
Here's a screen shot of the query result window showing types with more than 20 methods:
While CQL is already very powerful, I've been missing some features:
- aggregation - for example I'd like to calculate the max, min and average number of lines of code per method (update: while aggregates are not queryable, the query result window shows some aggregated values, see the screen shot above)
- comparing metrics - for example I cannot select methods with too many IL instructions per line (like "SELECT METHODS WHERE NbILInstructions > (NbLinesOfCode * 10)"); CQL won't allow me to compare NbILInstructions with anything other than integer numbers.
But all in all, CQL is a great idea and a powerful language. It is what makes NDepend such a versatile tool.
With this I'll conclude this short overview of Visual NDepend. The programs contains heaps of other features which you should discover for yourself.
The HTML report
Where Visual NDepend is used to setup a project and analyse it interactively, the HTML is meant to represent the state of a project in a static and concise way. The analysis data that NDepend generates is stored in an XML files. This has the advantage that you can simply use XSLT to transform the result into HTML. This is exactly what NDepend does to generate the HTML report. In Visual NDepend you can select to either provide your own xsl file or use the default transformation that NDepend comes with. The latter is certainly useful in most cases. If you need more control, go ahead and build your own xsl transformation file. This fits perfectly into NDepends philosophy: provide a useful default set of functionality, but be open for extensions!
So, what does the default report show:
- General application metrics
- lines of code
- number of IL instructions
- number of lines with comment, percentage comments
- number of assemblies, types, classes, interfaces, structs, etc.
- Percentage of public types and methods etc.
- Average number of fields per type, method per type etc.
- ...
- Metrics per assembly
- LOC, number of IL instructions, ...
- coupling metrics (Ca, Ce, relational cohesion, instability, abstractness, instability-abstractness-balance)
- Assembly dependencies
- CQL query & constraints results
- Warnings for constraints that have failed
- Type metrics
- LOC, number of IL instructions, ...
- coupling metrics (Ca, Ce, lack of cohesion ...)
- cyclomatic complexity
- Number of directly and indirectly derived classes, depth in inheritance tree
- Type dependencies (initially not enabled)
- Defines which types depend on which types.
The report also contains a dependency view (as in the Metrics window in Visual NDepend), a dependency graph (again, no 64bit support) and graph that show the balance between abstractness and stability.
(In the example project, the assemblies seem to be quite instable)
NDepend in automatic builds
You will most likely want to have NDepend generate a report during automatic builds; it's an invaluable tool to define metrics for code quality and to enforce design guidelines. NDepend comes with a command line tool (NDepend.Console.exe) that can be integrated into the build process. The command line tool is held simple: it simply uses a project file that you generated with Visual NDepend beforehand. While this makes it easy to configure an NDepend project at a central place, it has some drawbacks. NDepend stores only absolute paths, for example to folders that contain tested assemblies or to a previous build you want to compare the current build to. Update: the previous sentence is not true, NDepend supports a relative path mode. I simply overlooked the option the whole time. It can be found under Project properties => Code to analyze => Relative path mode:
While you can override the input folders and output folders with command line flags (/InDirs and /OutDir), other options in the project file cannot be overridden. This could cause trouble if you have a dedicated build server.
NDepend ships with an xslt for CruiseControl.NET and build tasks for nant and MsBuild. I haven't used any of the build tasks. I simply used the <exec> task in nant. Here's an example from one of my build files:
<property name="ndepend.project" value='"${root.dir}\NDependProject.xml"'/>
<property name="ndepend.outdir" value='${reports.dir}\ndepend'/>
<property name="ndepend.indirs" value='"${build.dir}"'/>
<property name="ndepend.indirs" value='${ndepend.indirs} "C:\Windows\Microsoft.NET\Framework\v2.0.50727"'/>
<property name="ndepend.indirs" value='${ndepend.indirs} "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"'/>
<property name="ndepend.indirs" value='${ndepend.indirs} "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5"'/>
<exec program="NDepend.Console.exe"
commandline='${ndepend.project} /InDirs ${ndepend.indirs} /OutDir "${ndepend.outdir}"'/>
Not too hard, if you ask me. The xsl file provided for CruiseControl creates a report that is similar to the HTML report you get when analysing using Visual NDepend, so I won't go into detail here. However, I promised you to show an example of a depency graph, so here we go:
Based on a blog post of Robin Curry I further improved ccnet integration, it now looks like this:
I needed to update Robin's XSL file to match the current version of NDepend. I plan to write a separate article on this "advanced" ccnet integration, so stay tuned!
Documentation, help, support
One thing I absolutely have to mention positively is the amount of help you get from NDepend. NDepend offers a plethora of tutorials (in video and text form), definition of all metrics, an in-depth specification of CQL, and a massive amount of Tips and Tricks. It is not often that you get that much of support!
Pricing
NDepend licenses are available starting from 299€ (excl VAT), with a massive discount depending on the number of licenses (down to 179€/license if you order more than 20 licenses). Furthermore Enterprise licenses are available on demand. See the purchase page for details.
Conclusion
Wow, that was a long article, wasn't it. Still I could only show you a fraction of the functionality NDepend has to offer. The cool thing is that you can do whatever fits your needs thanks to the extremely flexible and extensible design using CQL. Visual NDepend is a great user interface which makes analysing a project interactively easy fun and interesting. Integrate NDepend into your build process and you have heaps of metrics that you can use to quantify the quality of your code. The price is absolutely adequate.
Pros
- Extremely versatile and extensible, thanks to CQL
- Pinpoints problematic areas in your code
- Quantifies code quality - get rid of "I have the feeling that this and that piece of code is not optimal"
- Introduces a whole new language to the communication between developers
- Visual NDepend as a great GUI
- Large amount of tutorials
- Useful set of metrics to start with, extensible if needed
- Very convincing value for the money!
Cons and issues
- Dependency graphs not supported on x64 machines as of now
- CQL lacks some possibly interesting features (aggregates, comparison of metrics)
- NDepend.Console.exe has a limited set of parameters. It would be nice to be able to provide more options instead of relying on project files
Project files stores mostly absolute paths Update: not true, NDepend supports a relative path mode.
- An HTML report is always created, even in CI scenarios, where the XML files would have been enough.
Granted, none of the issues stated above are show stoppers. All in all there's no doubt that NDepend is an excellent tool. I can wholeheartedly recommend it to any developer who wants to improve the quality of his/her code.
Update 07/08/2008:
Patrick has just published a post in his blog in which he compares NDepend to other tools. I especially like the comparison to tools like Resharper or CodeRush:
I like to think that what tools such as ReSharper or CodeRush are doing to your code at micro level (i.e methods' body structuring), NDepend does it at macro level (i.e class, namespace, assembly structuring). Hence, as a developer I personally use both kind of tools to automatically control every aspects of the code base I am working on.
@Patrick: thanks for mentioning this post!
Things I updated:
- Rectified statement regarding absolute paths. NDepends does support a relative path mode
- Added screen shot of query result window and mentioned that aggregates are shown in that window
- Added link to Patrick's blog
Einfache Authentifizierung mit ASP.NET
07.07.2008 21:30:00
|
Jürgen Gutsch
Ich möchte heute mal eine einfache Authentifizierung mit ASP.NET vorstellen, welche ohne die herkömmlichen ASP.NET Login Controls und deren Provider auskommt. Der Vorteil ist, dass es für kleine Anwendungen wesentlich schneller und einfacher umzusetzen ist. Möchte man dagegen noch Dinge nutzen wie Rollen und Profile sind eher die herkömmlichen Controls und Membership-, Role- und ProfileProvider schneller zu implementieren. Der größte Nachteil bei der herkömmlichen Methode ist der, dass es recht aufwendig wird, sobald die Benutzer und Rollen aus einer Benutzerdefinierten und/oder bereits vorhandenen Datenbank kommen sollen, denn dann müssen in der Regel eigene Provider geschrieben werden.
Was wird benötigt?
1) Eine Datenquelle welche die Benutzerinformationen enthält
2) Eine öffentliche Loginseite oder eine öffentliche Seite mit einer Loginmöglichkeit
3) Mehrere geschützte Seiten
Die Datenquelle kann jede beliebige sein (XML, CSV, Access, SQL Server, etc...). In den Code-Beispielen gehe ich nicht auf diese ein, sondern verwende eine fiktive Klasse mit dem Namen "UserLib", welche mir dem Zugriff auf die DatenQuelle abnimmt.
Das Loginformular enthält ein Feld für die Benutzernamen, ein Feld für das Passwort, eine Checkbox um anzugeben, ob ein Cookie gesetzt werden soll und natürlich einen Button:
<asp:Panel ID="pnlLogin" runat="server"
GroupingText="Login" Width="300" BackColor="#FFFFFF">
<asp:Label AssociatedControlID="txtUsername" ID="lblUsername"
runat="server" Text="Benutzername" />
<br ID="txtUsername" runat="server" CssClass="text" />
<asp:Label AssociatedControlID="txtPassword" ID="lblPassword"
runat="server" Text="Passwort" />
<br ID="txtPassword" runat="server" CssClass="text" />
<br ID="chbRememberLogin" runat="server"
Text="Login Merken" />
<asp:Button ID="btnLogin" runat="server" Text="Anmelden"
OnClick="btnLogin_Click" />
<p>(Benutzername und Passwort: maxm)</p>
</asp:Panel>
Was passiert bei Klick auf den Button?
Wenn beide Felder ausgefüllt sind (zur Prüfung können selbstverständlich die Validator Controls verwendet werden) werden die Daten mit der Datenquelle gegen geprüft. Im Optimalfall ist das Passwort in der Datenquelle verschlüsselt. Das eingegebene Passwort wird als ebenfalls verschlüsselt und dem in der Datenquelle verglichen.
Sind die eingegebenen Daten falsch wird eine erneute Eingabe gefordert. Im anderen Fall wird jetzt erst mal die BenutzerID in einer Sessionvariablen gespeichert. Anschließend wird geprüft, ob die CheckBox für das Cookie angehakt ist. Wenn ja, wird ein Cookie mit der BenutzerID gesetzt. Das Cookie könnte eine Gültigkeit von ca. 14 Tagen haben.
Das war schon alles für den Loginvorgang. Man könnte jetzt auch schon auf eine geschützte Seite weiterleiten, je nach dem wie und wo das Loginformular eingebaut ist.
protected void btnLogin_Click(object sender, EventArgs e)
{
Guid UserID = UserLib.CheckUser(this.txtUsername.Text,
this.txtPassword.Text);
if (UserID == Guid.Empty)
{
// Fehlermeldung: Daten stimmen nicht
}
else
{
Session["UserID"] = UserID;
if (this.chbRememberLogin.Checked)
{
HttpCookie MyCookie = new HttpCookie("UserID");
MyCookie.Value = UserID.ToString();
MyCookie.Expires = DateTime.Now.AddDays(14);
Response.Cookies.Add(MyCookie);
}
Response.Redirect("Secure.aspx");
}
}
Wie wird der User erkannt?
Um mir die Arbeit nicht auf allen Seiten machen zu müsse, schreibe ich mir eine neue Basisklasse für diese Seiten. In meinem Beitrag über die Basisklasse ist die Lösung bereits teilweise umgesetzt. Was fehlte, ist die Prüfung des Cookies, wenn keine Sessionvariable existiert:
// Aktuelle Benutzer ID
private Guid userId = Guid.Empty;
public Guid UserId
{
get
{
if (userId == Guid.Empty)
{
object obj = Session["UserID"];
if (obj != null)
{
userId = new Guid(obj.ToString());
}
else
{
HttpCookie cookie = Request.Cookies["UserID"];
if (cookie != null)
{
string value = cookie.Value;
if (!string.IsNullOrEmpty(value))
{
userId = new Guid(value);
Session["UserID"] = userId;
}
}
}
}
return userId;
}
}
Theoretisch kann ich mir die Arbeit noch mehr vereinfachen, wenn ich mir die Ermittlung des aktuellen Users ebenfalls in diese Klasse holen (User ist hier eine einfache Klasse welche die Eigenschaften eines Benutzer enthält):
// Aktueller Benutzer
private User currenUser = null;
public User CurrentUser
{
get
{
if (currenUser == null)
currenUser = UserLib.LoadUser(this.UserId);
return currenUser;
}
}
In der Page_Init muss ich nur noch prüfen ob der aktuelle Benutzer nicht existiert und entsprechend z. B. auf eine Loginseite umleiten:
// Muss von öffentlichen Seiten überschrieben werden
protected virtual void Page_Init(object sender, EventArgs e)
{
if (this.CurrentUser == null)
Response.Redirect("Default.aspx", true);
}
Wenn der Benutzer als auf eine geschützte Seite trifft, wird erst geprüft, ob eine gültige Session existiert. Wenn nein, wird geprüft, ob ein Cookie existiert. Ist das auch nicht der Fall, kann die zweite Eigenschaft keinen Benutzer ermitteln, gibt Null zurück und es wird zum Login weitergeleitet. Existiert ein Cookie, wird die BenutzerID in die Session gespeichert und der Benutzer ist eingeloggt.
In öffentlichen Seiten muss die Page_Init der Basisklasse überschrieben werden, sonst entsteht eine Endlosschleife. Die Login Seite wird sich immer wieder selbst aufrufen, wenn "CurrentUser" null ist.
Die Loginseite könnte z. B: auf die erste geschützte Seite umleiten, wenn der Benutzer erkannt wurde:
protected override void Page_Init(object sender, EventArgs e)
{
if (this.CurrentUser != null)
Response.Redirect("Secure.aspx", true);
}
Anwendung
Von jetzt an kann ich in allen geschützten Seiten, welche von dieser Basisklasse ableiten, auf die Eigenschaften des aktuellen Benutzers zugreifen:
this.lblFullName.Text = String.Format("{0} {1}",
this.CurrenUser.Name, this.CurrenUser.LastName);
Demoprojekt
Ein kleines Demoprojekt kann hier heruntergeladen werden:
http://www.aspnetzone.de/files/folders/198986/download.aspx
(Es wird das .NET Framework 3.5 verwendet)
Einfache Authentifizierung mit ASP.NET
07.07.2008 21:30:00
|
Jürgen Gutsch
Ich möchte heute mal eine einfache Authentifizierung mit ASP.NET vorstellen, welche ohne die herkömmlichen ASP.NET Login Controls und deren Provider auskommt. Der Vorteil ist, dass es für kleine Anwendungen wesentlich schneller und einfacher umzusetzen ist. Möchte man dagegen noch Dinge nutzen wie Rollen und Profile sind eher die herkömmlichen Controls und Membership-, Role- und ProfileProvider schneller zu implementieren. Der größte Nachteil bei der herkömmlichen Methode ist der, dass es recht aufwendig wird, sobald die Benutzer und Rollen aus einer Benutzerdefinierten und/oder bereits vorhandenen Datenbank kommen sollen, denn dann müssen in der Regel eigene Provider geschrieben werden.
Was wird benötigt?
1) Eine Datenquelle welche die Benutzerinformationen enthält
2) Eine öffentliche Loginseite oder eine öffentliche Seite mit einer Loginmöglichkeit
3) Mehrere geschützte Seiten
Die Datenquelle kann jede beliebige sein (XML, CSV, Access, SQL Server, etc...). In den Code-Beispielen gehe ich nicht auf diese ein, sondern verwende eine fiktive Klasse mit dem Namen "UserLib", welche mir dem Zugriff auf die DatenQuelle abnimmt.
Das Loginformular enthält ein Feld für die Benutzernamen, ein Feld für das Passwort, eine Checkbox um anzugeben, ob ein Cookie gesetzt werden soll und natürlich einen Button:
<asp:Panel ID="pnlLogin" runat="server"
GroupingText="Login" Width="300" BackColor="#FFFFFF">
<asp:Label AssociatedControlID="txtUsername" ID="lblUsername"
runat="server" Text="Benutzername" />
<br ID="txtUsername" runat="server" CssClass="text" />
<asp:Label AssociatedControlID="txtPassword" ID="lblPassword"
runat="server" Text="Passwort" />
<br ID="txtPassword" runat="server" CssClass="text" />
<br ID="chbRememberLogin" runat="server"
Text="Login Merken" />
<asp:Button ID="btnLogin" runat="server" Text="Anmelden"
OnClick="btnLogin_Click" />
<p>(Benutzername und Passwort: maxm)</p>
</asp:Panel>
Was passiert bei Klick auf den Button?
Wenn beide Felder ausgefüllt sind (zur Prüfung können selbstverständlich die Validator Controls verwendet werden) werden die Daten mit der Datenquelle gegen geprüft. Im Optimalfall ist das Passwort in der Datenquelle verschlüsselt. Das eingegebene Passwort wird als ebenfalls verschlüsselt und dem in der Datenquelle verglichen.
Sind die eingegebenen Daten falsch wird eine erneute Eingabe gefordert. Im anderen Fall wird jetzt erst mal die BenutzerID in einer Sessionvariablen gespeichert. Anschließend wird geprüft, ob die CheckBox für das Cookie angehakt ist. Wenn ja, wird ein Cookie mit der BenutzerID gesetzt. Das Cookie könnte eine Gültigkeit von ca. 14 Tagen haben.
Das war schon alles für den Loginvorgang. Man könnte jetzt auch schon auf eine geschützte Seite weiterleiten, je nach dem wie und wo das Loginformular eingebaut ist.
protected void btnLogin_Click(object sender, EventArgs e)
{
Guid UserID = UserLib.CheckUser(this.txtUsername.Text,
this.txtPassword.Text);
if (UserID == Guid.Empty)
{
// Fehlermeldung: Daten stimmen nicht
}
else
{
Session["UserID"] = UserID;
if (this.chbRememberLogin.Checked)
{
HttpCookie MyCookie = new HttpCookie("UserID");
MyCookie.Value = UserID.ToString();
MyCookie.Expires = DateTime.Now.AddDays(14);
Response.Cookies.Add(MyCookie);
}
Response.Redirect("Secure.aspx");
}
}
Wie wird der User erkannt?
Um mir die Arbeit nicht auf allen Seiten machen zu müsse, schreibe ich mir eine neue Basisklasse für diese Seiten. In meinem Beitrag über die Basisklasse ist die Lösung bereits teilweise umgesetzt. Was fehlte, ist die Prüfung des Cookies, wenn keine Sessionvariable existiert:
// Aktuelle Benutzer ID
private Guid userId = Guid.Empty;
public Guid UserId
{
get
{
if (userId == Guid.Empty)
{
object obj = Session["UserID"];
if (obj != null)
{
userId = new Guid(obj.ToString());
}
else
{
HttpCookie cookie = Request.Cookies["UserID"];
if (cookie != null)
{
string value = cookie.Value;
if (!string.IsNullOrEmpty(value))
{
userId = new Guid(value);
Session["UserID"] = userId;
}
}
}
}
return userId;
}
}
Theoretisch kann ich mir die Arbeit noch mehr vereinfachen, wenn ich mir die Ermittlung des aktuellen Users ebenfalls in diese Klasse holen (User ist hier eine einfache Klasse welche die Eigenschaften eines Benutzer enthält):
// Aktueller Benutzer
private User currenUser = null;
public User CurrentUser
{
get
{
if (currenUser == null)
currenUser = UserLib.LoadUser(this.UserId);
return currenUser;
}
}
In der Page_Init muss ich nur noch prüfen ob der aktuelle Benutzer nicht existiert und entsprechend z. B. auf eine Loginseite umleiten:
// Muss von öffentlichen Seiten überschrieben werden
protected virtual void Page_Init(object sender, EventArgs e)
{
if (this.CurrentUser == null)
Response.Redirect("Default.aspx", true);
}
Wenn der Benutzer als auf eine geschützte Seite trifft, wird erst geprüft, ob eine gültige Session existiert. Wenn nein, wird geprüft, ob ein Cookie existiert. Ist das auch nicht der Fall, kann die zweite Eigenschaft keinen Benutzer ermitteln, gibt Null zurück und es wird zum Login weitergeleitet. Existiert ein Cookie, wird die BenutzerID in die Session gespeichert und der Benutzer ist eingeloggt.
In öffentlichen Seiten muss die Page_Init der Basisklasse überschrieben werden, sonst entsteht eine Endlosschleife. Die Login Seite wird sich immer wieder selbst aufrufen, wenn "CurrentUser" null ist.
Die Loginseite könnte z. B: auf die erste geschützte Seite umleiten, wenn der Benutzer erkannt wurde:
protected override void Page_Init(object sender, EventArgs e)
{
if (this.CurrentUser != null)
Response.Redirect("Secure.aspx", true);
}
Anwendung
Von jetzt an kann ich in allen geschützten Seiten, welche von dieser Basisklasse ableiten, auf die Eigenschaften des aktuellen Benutzers zugreifen:
this.lblFullName.Text = String.Format("{0} {1}",
this.CurrenUser.Name, this.CurrenUser.LastName);
Demoprojekt
Ein kleines Demoprojekt kann hier heruntergeladen werden:
http://www.aspnetzone.de/files/folders/198986/download.aspx
(Es wird das .NET Framework 3.5 verwendet)

Softwarezellen - oder: Wie baue ich eine Architektur?
07.07.2008 13:45:20
|
Rainer Schuster
Ralf Westphal
... berichtet schon seit längerem über Architektur und seine Gedanken dazu. 2005 hat er mit einem neuen Model für sich angefangen, da ihm die bisher übliche Schichtenarchitektur nicht ausreichend war. In der dotnetpro finden wir auch immer wieder Artikel von ihm, die mit diesem System angereichert sind.
Und ich?
Persönlich habe ich auch schon Programmarchitekturen damit zerlegt und erstellt und muss sagen das dieses System - hat man es einmal verstanden - sehr eingängig ist und schon bei der ersten Anwendung einen großen "Benefit" bringt. In jedem Fall findet - so bisher meine Erfahrung - eine win-win Situation statt. Ich kann meinem Vorgesetzten oder dem Stakeholder meine reflektiven Fähigkeiten beweisen und mein Gegenüber bekommt im Gegenzug ein einfaches und selbsterklärendes Diagramm der jeweiligen Architektur.
Zurück zum Thema.
Des öfteren berichtet er in seinem deutschen, sowie englischen Blog über die Evolution seines Systems. Nun widmet er sich in einem neuen, dedizierten Blog (The Architect's Napkin) zu diesem Thema. Als eine Art Journal wird er es zum Sammeln seiner Ideen für ein Buch nutzen und uns seine Gedanken damit näher bringen. Grundgedanke und Medium für die Konstruktion der Ideen sind Servietten. Alles was nicht auf eine passt, ist nicht wertvoll und zu überladen und in der Endkonsequenz damit unbrauchbar. Wie bekomme ich dann eine Architektur auf eine Serviette? Das Abstraktionslevel wird von Serviette zu Serviette gesteigert. Aber lest selber nach. Es lohnt sich für alle Architekturinteressierten. Er listet in einem Beitrag die Fragen auf, die sich der Architekt stellen sollten, wenn es darum geht eine Softwarezelle zu erstellen. Nun finde ich, ist dieser kleine Fragenkatalog abgesehen davon auch für Softwareentwickler sinnvoll. Darum einmal hier die Auflistung von ihm:
- How is the software cell started and stopped?
- Can the software cell be paused or interrupted?
- How are processing failures reported to the environment of the software cell?
- How should data be passed into and out of the software cell?
- How is shared data protected from inconsistencies through concurrent access by several software cells?
- How can I be informed of certain states during a process executed by a software cell?
- How can I know if a software cell is still alive and progressing?
- How fast, reliable, secure etc. is a software cell and a connection to it?
- Does it matter to a client of a software cell, where and if the software cell is running?
ASP.NET Hacks
07.07.2008 13:09:33
|
Jürgen Gutsch
ASP.NET Hacks
07.07.2008 13:09:33
|
Jürgen Gutsch
Dieses Blog steht unter einer Creative Commons-Lizenz
07.07.2008 10:41:25
|
Albert Weinert
Shared Memory in verwaltetem Code
06.07.2008 18:23:51
|
Klaus Bock
Zum Austausch von Daten zwischen verschiedenen Prozessen bietet das .NET Framework diverse Möglichkeiten im Namensraum System.Runtime.Remoting und den untergeordneten Namensräumen. Jedoch habe alle diese Klassen eins gemein: mit Ausnahme des System.Runtime.Remoting.Channels.Ipc-Namensraum verwenden sie alle Channels der verschiedenen Netzwerk-Protokolle. Als ich begann mich mit Interprozesskommunikation in verwaltetem Code zu beschäftigen, gab es den Ipc-Namensraum im .NET Framework noch nicht. Doch wenn ich mir heute die Verwendung der IpcChannel Klasse anschaue, ist mir das viel zu umständlich. Da müssen URIs angegeben und Objekte für Remote-Aufrufe zur Verfügung gestellt und geparst werden. Ich will doch nur ein paar Daten zwischen zwei Prozessen austauschen! In nicht verwaltetem C++ gab und gibt es Shared Memory, einen gemeinsam genutzten Speicherbereich auf den von beiden Prozessen zugegriffen werden kann. Da die Win32-API diese Möglichkeit bietet sollte dieses Prinzip doch auch in verwaltetem Code zu realisieren sein. Wie in folgendem kleinem Video zu sehen ist, funktioniert es wunderbar. Zum Testen des Shared Memory habe ich ein Projekt mit zwei Konsolenanwendungen erstellt. Die eine Anwendung, welche als Client fungiert, startet die zweite Anwendung als Server und liest die Werte welche von der Server-Anwendung in den Shared Memory geschrieben werden.
Die Initialisierung der SharedMemory Klasse ist denkbar einfach. Wenn in das Shared Memory Segment geschrieben werden soll, wird der Konstruktor der Klasse mit dem Namen des Segments und dem einzufügenden Objekt aufgerufen.
SharedMemory sm = new SharedMemory("testing", obj);
Wenn aus dem Shared Memory Segment nur gelesen werden soll, wird ein Konstruktor lediglich mit dem Namen des Shared Memory Segment aufgerufen.
SharedMemory sm = new SharedMemory("testing");
Um das schreiben in und das lesen aus dem Shared Memory Segment threadsicher zu gestalten, stellt die Shared Memory Klasse die öffentlichen Methoden Lock() und Unlock() bereit. Diese sperren den verwendeten Mutex der Klasse bzw. geben ihn wieder frei. So wird ein Objekt wie folgt in das Shared Memory Segment geschrieben:
sm.Lock();
sm.AddObject(value);
sm.Unlock();
und so wird ein Objekt aus dem Shared Memory Segment gelesen:
sm.Lock();
object obj = sm.GetObject();
sm.Unlock();
So einfach kann IPC (inter-process communication) sein.
Soweit zur Verwendung der Shared Memory Klasse.
Um die Funktionsweise von Shared Memory unter Windows zu verstehen, muss man sich erst ein wenig mit der Speicherverwaltung von Windows auseinandersetzen. Unter Windows läuft die gesamte Speicherverwaltung über den System eigenen Memory Manager der für jeden Prozess einen eigenen virtuellen Adressraum zur Verfügung stellt. Dieser virtuelle Adressraum entspricht aber nicht der physikalischen Adresse im Speicher. Er muss sich gar nicht im Hauptspeicher befinden, sondern kann in die Auslagerungsdatei ausgelagert worden sein. Wenn jetzt ein Prozess auf einen Bereich seines virtuellen Adressraum's zugreift, weis die Speicherverwaltung von Windows wo sich der zugehörige physikalische Adressbereich befindet und gibt diesen and den Prozess zurück. Mit Hilfe der Win32-API kann nun ein gemeinsamer Speicherbereich für mehrere Prozesse geschaffen werden, der physikalisch nur einmal vorhanden ist. Unter Windows werden diese Speicherbereiche File Mapping Objekte genannt. In so einem Objekt wird ein Teil oder auch eine gesamte Datei im physikalischen Speicher abgebildet. Die Speicherverwaltung kann jetzt einen angegebenen oder auch den gesamten Speicherbereich im virtuellen Speicher eines oder mehrere Prozesse zur Verfügung stellen.
Wenn man diese Information bedenkt, wird vielleicht klar, warum in der Win32-API die Verwendung von Shared Memory und Memory-Mapped Files in einer API zusammengefasst sind. Es spielt also keine Rolle ob man ein Shared Memory Segment verwendet oder den Inhalt einer bestimmten Datei als File Mapping abbildet; es ist immer die gleiche API. Es braucht sich also niemand zu wundern wenn die Funktionen CreateFileMapping oder OpenFileMapping verwendet werden obwohl keine Datei vorhanden ist die im Speicher abgebildet werden soll.
Da ich mir sicher war, dass bestimmt schon jemand vor mir auf die Idee gekommen war Shared Memory in verwaltetem Code zu verwenden suchte ich Informationen zu einer möglichen Implementierung. Mehr durch Zufall fand ich ein paar Zeilen dazu, sowie ein Demo-Projekt zum Download, in Richads Blewett's altem Blog. Seine Segment Klasse gefiel mir auf den ersten Blick sehr gut. Das erzeugen bzw. öffnen eines File Mapping Objekts wird im Konstruktor erledigt. Der Zeiger auf das Shared Memory Segment wird im Konstruktor erzeugt und in einer Klassen Member Variablen vom Typ IntPtr gehalten. Das kopieren der Daten in und aus dem Segment wird mit einem Stream erledigt.
Was mir nicht gefiel, war die Verwendung von unsafe Pointern und die Verwendung der fixed-Anweisung. Hier die original Methoden welche Daten in und aus einem Shared Memory Segment kopieren:
/// <summary>
/// Copies stream to shared memory segment using unsafe pointers
/// </summary>
/// <param name="stream"> System.IO.Stream - data to be copied to shared memory</param>
private unsafe void CopyStreamToSharedMemory( Stream stream )
{
// Read stream data into byte array
BinaryReader reader = new BinaryReader(stream);
Byte[] data = reader.ReadBytes((int)stream.Length);
// Copy the byte array to shared memory
fixed( byte* source = data )
{
void* temp = nativePointer.ToPointer();
byte* dest = (byte*)temp;
Win32Native.CopyMemory((int) dest, (int) source, (int)stream.Length);
}
}
/// <summary>
/// Copies shared memory data to passed stream using unsafe pointers
/// </summary>
/// <param name="stream">System.IO.Stream - stream to receive data</param>
private unsafe void CopySharedMemoryToStream( Streamstream )
{
// Create a tempory byte array to store the length
void* temp = nativePointer.ToPointer();
byte* source = (byte*)temp;
long len = *(long*)temp;
// Set the source data pointer to start of serialized object graph
source = (byte*)temp;
source += 8;
// Create a byte array to hold the serialized data
Byte[] data = new Byte[len];
// Copy the shared memory data to byte array
fixed(byte* dest = data )
{
Win32Native.CopyMemory((int)dest, (int)source, (int)len);
}
// Write the byte array to the stream
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(data);
// Reset stream to start
stream.Seek(0, SeekOrigin.Begin);
}
Dies ist eine gutes Beispiel um zu zeigen wie sich der Umgang mit Zeigern und das kopieren von Speicherbereichen durch Verwendung der Marshal Klasse stark vereinfachen lässt sowie auf unsafe und fixed komplett verzichtet werden kann. Vor allem die Methode Marshal.Copy ersetzt die Win32-API Funktion CopyMemory ohne von P/Invoke Gebrauch machen zu müssen.
private void copyStreamToSharedMemory(Stream stream)
{
// die Stream-Daten in ein byte Array lesen
BinaryReader reader = new BinaryReader(stream);
byte[] data = reader.ReadBytes((int)stream.Length);
// kopiere das byte Array in den SharedMemory
Marshal.Copy(data, 0, this.nativePointer, (int)stream.Length);
}
Ähnlich verhält es sich auch mit der Verwendung der Zeiger in der zweiten Methode CopySharedMemoryToStream. In der Original-Implementierung wird ein Konstrukt aus Zeigern verwendet um die Länge der Daten im Shared Memory Segment zu ermitteln sowie einen Zeiger auf das Shared Memory Segment zu erhalten. Als erstes wird mit
void* temp = nativePointer.ToPointer();
ein Zeiger auf einen Speicherbereich erzeugt, von dem man nicht weis welchen Typs die enthaltenen Daten sind. Wenn jetzt die Speicheradresse bekannt ist, wird mit
long len = *(long*)temp;
ein Zeiger vom Typ long erzeugt um die Länge des Speicherbereichs zu lesen.
Nun wird noch der erzeugte Zeiger in einen Zeiger auf eine nicht verwaltete Speicheradresse geparst um die enthaltenen Daten aus dem Shared Memory Segment kopieren zu können:
byte* source = (byte*)temp;
Mit der Marshal Klasse lässt sich das sehr elegant auch ohne unsafe Zeiger erledigen:
// die Länge der Daten im SharedMemory Segment bestimmen.
long objLength = (long)Marshal.ReadIntPtr(this.nativePointer);
// Zeiger auf das SharedMemory Segment erzeugen.
IntPtr source = (IntPtr)((long)this.nativePointer + sizeof(long));
Wahrscheinlich fragt sich hier der ein oder andere warum so ein Aufwand mit der Länge der Daten im Shared Memory Segment betrieben wird. Nun ganz einfach: um die Daten aus dem Shared Memory Segment zu kopieren benötigt man ein byte-Array. Dieses Bayre-Array muss mit einer bestimmten Länge erzeugt werden um alle Daten aufnehmen zu können. Woher soll also die Information genommen werden, wie groß die Datenmenge im Shared Memory Segment ist? Um dies zu handhaben wird beim kopieren der Daten in das Shared Memory Segment an erster Stelle im Stream die Größe des serialisierten Objekts als long-Wert geschrieben und direkt im Anschluss das eigentliche Objekt in den Stream serialisiert.
Aus dem gleichen Grund wird auch ein Offset von der Länge des Typs long bei der Erzeugung des Zeigers auf das Shared Memory Segment verwendet. Da ja, wie oben erwähnt, die ersten acht byte der Daten im Speicher mit der Länge des Objekts belegt sind muss der Zeiger um acht byte, die Länge des Typs long, nach hinten verschoben werden.
Hier nun die komplette Methode zum kopieren der Daten des Shared Memory Segment in einen Stream:
private void copySharedMemoryToStream(Stream stream)
{
// die Länge der Daten im SharedMemory Segment bestimmen.
long objLength = (long)Marshal.ReadIntPtr(this.nativePointer);
// Zeiger auf das SharedMemory Segment erzeugen.
IntPtr source = (IntPtr)((long)this.nativePointer + sizeof(long));
// ein byte Array mit der Länge des Speicherbereichs
// erzeugen um die serialisierten Daten aufzunehmen.
byte[] data = new byte[objLength];
// die SharedMemory Daten in das byte Array kopieren
Marshal.Copy(source, data, 0, (int)objLength);
// neue BinaryWriter erzeugen
BinaryWriter writer = new BinaryWriter(stream);
// das byte Array in den Stream schreiben
writer.Write(data);
// den Stream auf seinen Startpunkt setzen
stream.Seek(0, SeekOrigin.Begin);
}
Dies sind die beiden privaten Methoden, welche transparent das kopieren der Daten in und aus dem Shared Memory Segment erledigen. Auf die anderen Member der Klasse sowie auf weitere verwendete Methoden der Win32-API will ich hier nicht näher eingehen. Die Grundlage und die entscheidenden Methoden habe ich gezeigt. Für interessierte deren Neugier ich geweckt habe, steht ein Demo-Projekt zum Download bereit. Der Code im Demo-Projekt ist weitestgehend dokumentiert.
IpcTest
Der Einsatz von HTTP 301 und 302 Statuscodes in ASP.NET
06.07.2008 01:32:58
|
Jan Welker
Die Methode Response.Redirect() kennt vermutlich jeder ASP.NET Entwickler. Mit Hilfe
dieser Methode wird ein Seitenbesucher auf eine andere Webseite umgeleitet. Doch was
steckt dahinter? Wann sollte man die Redirect Methode nutzen und wann nicht?
Schauen wir uns an was passiert, wenn folgender Code ausgeführt wird:
Response.Redirect("http://dotnet-snippets.de/dns/Default.aspx");
Der Webserver antwortet dem Client mit dem HTTP Statuscode 302, dies bedeutet das
die Webseite temporär umgeleitet wurde.
Zitat von Wikipedia:
Die angeforderte Ressource steht vorübergehend unter der im „Location“-Header-Feld
angegebenen Adresse bereit (in HTTP/1.0 „Moved Temporarily“, RFC 1945). Die alte Adresse
bleibt gültig.
Im Trace sieht dies folgendermaßen aus:
Wenn eine Seite temporär unter einer anderen Adresse erreichbar ist, sollte
Response.Redirect() verwendet werden. Ist eine Seite zukünftig immer unter
einer anderen Adresse erreichbar, sollte der HTTP Statuscode 301 verwendet werden.
Der Statuscode 301 steht für eine permanente Umleitung. Zitat von Wikipedia:
Die angeforderte Ressource steht ab sofort unter der im „Location“-Header-Feld
angegebenen Adresse bereit. Die alte Adresse ist nicht länger gültig.
Dies ist sehr wichtig für Suchmaschinen, denn somit bekommen Suchmaschinen die
Information, dass die alte URL aus dem Index entfernt werden kann.
Wie kann erreicht werden, dass der Statuscode 301 gesendet wird?
HttpContext.Response bietet hierzu die Eigenschaft Status und die Methode AddHeader an:
HttpContext.Current.Response.Status = "301
Moved Permanently"; HttpContext.Current.Response.AddHeader("Location",
newUrl);
Um diese 301 - Weiterleitung jederzeit komfortabel nutzen zu können bietet sich eine Extension
Method an:
using System.Web; namespace Welker.Extensions
{ public static class Extensions
{ public static void RedirectPermanently(this HttpResponse
response, string newUrl) { HttpContext.Current.Response.Status
= "301 Moved Permanently"; HttpContext.Current.Response.AddHeader("Location",
newUrl); } } }
Nach dem Einbinden der Erweiterungsmethode kann diese über folgenden Code aufgerufen
werden:
Response.RedirectPermanently("http://live.com");
Im Trace kann man den Unterschied deutlich sehen:
Have Fun!
SQL Injection – wenn man mal wieder sein Kennwort vergessen hat…
05.07.2008 17:02:24
|
Mathias Raacke
Gerade wollte ich mich zu einem meiner Produkte auf der Support-Webseite einloggen.
Dummerweise ist mir mal wieder mein Kennwort für diese Seite nicht eingefallen (keine
Ahnung, ob ich überhaupt schon eins habe). Ohne wirklich mit einem Ergebnis zu rechnen
habe ich einfach mal folgendes eingegeben:
Benutzer: %
Passwort: ' OR 1=1 --
Ergebnis: “Welcome to the […] Web Site of the Technical Support”.
Wie lange wird es wohl noch dauern, bis das Thema SQL Injection endlich bei jedem
Entwickler angekommen ist? Oder Sicherheit ganz allgemein?
Naja, wenigstens muss ich mir für diese Seite kein Kennwort mehr merken.
Nachtrag:
Gerade habe ich mir mal aktuelle Folien zu einer Datenbankvorlesung bei uns in Paderborn
angesehen. Ist leider tatsächlich noch so wie vor Jahren als ich die Vorlesung selbst
gehört habe – kein Wort von SQL Injection. Dafür folgendes:
Solange selbst an der Uni das Thema “Security” in der Softwareentwicklung überhaupt
keine Rolle spielt, darf man sich nicht wundern dass solche grundlegenden Sicherheitslücken
immer wieder in Web Seiten auftauchen.
Einzeilige Methoden?
05.07.2008 16:06:49
|
Albert Weinert
In Stelle immer wieder fest dass es eine ziemlich eingefahrene Meinung dazu gibt ab wann sich eine eigene Methode lohnt und wann nicht. Meist heißt es dass für eins bis drei Zeilen keine Methode notwendig ist und man durch einen Methoden Aufruf ja auch Performance Einbußen hat.
Ja, dieser Ansicht war ich früher auch. Problem an der Sache ist einfach, dass man auch bei wenig Zeilen oft manchmal nicht direkt den Zweck dahinter erkennt. Also müsste man entsprechende Kommentare schreiben die erklären was dort eigentlich passiert.
Hier ein kleines Beispiel welches ich gerade geschrieben hatte.
public void Register(Type interfaceType, Type implementationType, bool isStatic)
{
lock (servicesLock)
{
EnsureValidInterface(interfaceType);
if (implementationType.FindInterfaces(
(m, criteria) => m.Equals(interfaceType), null).Length == 0)
{
// Do Some Magic
}
services.Add(interfaceType, new IoCInfo(implementationType, isStatic));
}
}
Hier muss dass if-Statement schon sehr genau angesehen werden damit einem klar wird was dort eigentlich überprüft wird. Somit wird der Fluss beim Lesen doch sehr gestört weil ich erst einmal analysieren muss was da passiert.
Ein kleiner Kommentar drüber und schon wüsste man was passiert. Der Kommentar müsste ungefähr so lauten "Wird das angegebene Interface implementiert?". Doch Hand auf's Herz, machen wir Entwickler dies wirklich? Des weiteren lesen wir doch noch den Code mit und versuchen ihn zu verstehen, somit haben wir nicht wirklich etwas gewonnen.
Jedoch besteht auch die Möglichkeit das if-Statement in eine eigene Methode auszulagern, deren Bezeichnung dazu noch ausdrückt was darin gemacht wird.
private bool InterfaceIsImplemented(Type interfaceType, Type implementationType)
{
return implementationType.FindInterfaces(
(m, criteria) => m.Equals(interfaceType), null).Length > 0;
}
Ein Kommentar bei der Verwendung wird nicht mehr gebraucht, die Methode spricht für sich selbst.
public void Register(Type interfaceType, Type implementationType, bool isStatic)
{
lock (servicesLock)
{
EnsureValidInterface(interfaceType);
if (!InterfaceIsImplemented(interfaceType, implementationType))
{
// Do some Magic
}
services.Add(interfaceType, new IoCInfo(implementationType, isStatic));
}
}
Es wird direkt beim lesen des Codes deutlich was man prüft. Dass wie es gemacht wird, interessiert mich erst wenn es sich andersartig verhält. Des weiteren hat man ohne großen Aufwand die Möglichkeit geschaffen den Code an mehreren Stellen zu verwenden ohne in in per Copy & Paste zu vervielfältigen.
Die Performance-Frage ist in den allermeisten Fällen zu vernachlässigen. An erster Stelle sollte Funktion, Lesbarkeit sowie einfache Wartung des Codes stehen. Tritt dann ein Performance Problem auf so kann man sich dann immer noch damit beschäftigen, jedoch nicht ohne wirklich mit einem Profiler nachzumessen, da die Performance oft nicht da liegen bleibt wo man meint.
Nun mag man noch es wäre ja erhöhter Aufwand "so viel" in einzelnen Methoden zu packen, dem kann ich nur widersprechen. Visual Studio bietet seit der Version 2005 eingebautes Refactoring mit dem man mit einem Shortcut eine entsprechende Methode aus dem Code extrahieren kann. Beim schreiben des Codes kann man dies auch berücksichtigen den unbekannte Methodenrümpfe sind auch per Shortcut zu erstellen.
Es gibt AddIns wie ReSharper von JetBrains oder ReFactor! von DevExpress, die weit über die Möglichkeiten von Visual Studio hinausgehen und ein Vielfältigeres Refactoring des Codes erlauben.

HowToCode "ReadYou": Neue Gedanken zum Usersystem mit OpenID, Windows Live und co.
03.07.2008 23:38:03
|
Robert Mühsig
Mein letzter direkter “ReadYou” Post ist schon etwas länger her - meine Membership-Idee habe ich in einem HowTo mal verwirklicht, allerdings hatte ich bisher keine Gelegenheit mehr zu coden.
Authentication, Authorization & Personalization
Für mich persönlich war der Kommentar von Andre Loker ein Anstoß darüber nachzudenken, das Membership System komplett zu entfernen bzw. einfach meinen eigenen Provider zu schreiben. Die verschiedenen Tabellen vom Membership-System find ich recht “groß” und unübersichtlich.
Insbesondere passt aber das Membership-System nicht ganz in meine Architektur rein.
Gut, dass nicht nur ich so denke, sondern inzwischen auch Rob Conery zu diesem Ergebnis gekommen ist. Den Anstoß gab Ayende Rahien in dem 15 Teil vom MVC Storefront Screencast.
Inzwischen hat Rob den 16 Teil fertig gestellt - in diesem verwirft er das Membership-System und implementiert prototypisch OpenID.
Hinweis: Meine Anwendung wird stark an die Architektur von Rob angelehnt sein - ich hab dies bereits produktiv in einem Projekt eingesetzt und bin positiv überrascht wie gut dies geklappt hat
Die gesamte MVC Storefront Serie ist sehr sehenswert!
Robs Screencast endet bei dem Login mit OpenID - er erwähnt noch, dass er sich nun auch Gedanken macht, wie er die OpenID tatsächlich an einen User knüpfen kann. Selbe Gedanken beschäftigen mich ebenfalls schon eine Weile (und insbesondere habe ich ja noch den mutigen Vorschlag gehabt Windows Live ID mit zu unterstützen
).
Was es nicht heute alles gibt
Ich muss zugeben, dass ich früher mir nicht so einen großen Kopf um das Usersystem an sich gemacht hatte - da wird das Passwort und ein Loginname vergeben und gut ist.
Allerdings bin ich der Meinung, dass hier einige große Veränderungen kommen - warum sich bei X-Diensten registrieren, wenn man doch z.B. eine OpenID hat.
Auch CardSpace ist eine Sache - Produktiv hab ich das (bis auf den Blog Kim Cameron) nicht gesehen.
Jetzt eine Frage an meine werte Leserschaft: Wer von euch weiß was OAuth ist und was man damit machen kann? So richtig schlau werde ich aus der Seite nicht.
Wo stehen wir momentan?
Momentan beschäftigt mich das Grundkonzept von diesen verschiedenen Diensten etwas - da mir leider da auch TDD nicht weiterhelfen kann, versuche ich mich erstmal zu informieren.
Ein paar interessante Links (neben den MVC Storefront) :
Das wäre es (leider) für heute erst mal.
Ein Hinweis noch: Mir ist bewusst, dass ich mich doch recht lange an einem doch trivial klingenden Thema aufhalte - allerdings genau wegen solchen Sachen mach ich das ja auch. Wer würde das denn sonst auch bezahlen?
ShareThis
Neue Version von Moonlight verfügbar (Silverlight für Linux)
03.07.2008 16:03:00
|
Steffen Ritter
Die Entwicklung von Moonlight (die offizielle Linux-Version von Silverlight) schreitet mit riesen Schritten voran. Das freut mich ganz besonders, denn gerade in Deutschland war die Frage nach einer Linux-Version von Silverlight von Anfang an eines der Hauptthemen in vielen Interviews und Gesprächen die ich zu Silverlight geführt hatte. Auch heute noch scheint sich in diversen hei(s)sen Foren hartnäckig das Gerücht zu halten „dieses Silverlight gibt’s ja nur für Windows“ – aber die Zahl der Informierten wächst permanent. Noch einmal für alle: Silverlight für Mac OS X und Windows wird von Microsoft selbst entwickelt, Silverlight für Linux heisst Moonlight und wird von dem Mono-Team bei Novell mit Unterstützung von Microsoft programmiert. Die neue Version ist auf der Moonlight-Homepage verfügbar: http://www.go-mono.com/moonlight
Einige der Neuerungen und Verbesserungen:
- Unterstützung für Webkit-basierte Browser
- Fehler im Windowsless Mode wurden behoben
- Fehler beim Event Handling wurden behoben
- Unzählige Fehler im Clock/Animation Framework wurden behoben; Moonlight besteht nun beide Animation Matrix Tests
- Fehler im Parser wurden behoben
- Mausprobleme wurden behoben
- Verbesserungen bei der Verarbeitung von Medien/Streaming
- Drastische Performanceverbesserungen
Moonlight ist momentan noch Stand Silverlight 1.0, aber erste (einfache) Silverlight 2-Anwendungen laufen bereits mit Moonlight
Load a font from disk, stream or byte array
03.07.2008 11:30:32
|
Andre Loker
A user asked in the Gamedev.net Forums how to load a Font from a Stream or a byte array. An interesting question, I think, because there might be applications that come with their own fonts. The obvious way to make the font available is by installing it to the font collection of the O/S. In that case one could simply use the Font constructor that takes the font family name as its first argument and be happy.
However, there are several good reasons to decide against installing a new font to the O/S:
- Installing a font requires administrative privileges
- You don't want to clutter the font collection unnecessarily
- Your app does not have or need an installer because it's a very simple tool. You neither want to add an installer just for installing the font, nor do you want the user to manually install a font.
Basics: load font from file
Therefore, the first thing we would like is to load a true type font file (ie. a .ttf file) directly from disk, without the need to install it first. To achieve this, the .NET framework provides the class System.Drawing.Text.PrivateFontCollection. The method AddFontFile does just what we want: load a ttf file. The Families property returns an array of all font families that have been loaded so far.
The method below shows a method that loads a font file and returns the first font family:
public static FontFamily LoadFontFamily(string fileName, out PrivateFontCollection fontCollection) {
fontCollection = new PrivateFontCollection();
fontCollection.AddFontFile(fileName);
return fontCollection.Families[0];
}
The returned FontFamily can then be used to construct specific fonts, like this:
PrivateFontCollection fonts;
FontFamily family = LoadFontFamily("TheFont.ttf", out fonts);
Font theFont = new Font(family, 20.0f);
// when done:
theFont.Dispose();
family.Dispose();
family.Dispose();
You can then use the font as usual.
Next level: load font from stream or byte array
If you want to deploy your application as a single .exe you would prefer to load the font from an embedded resource rather than loading it from a separate file. PrivateFontCollection provides a method called AddMemoryFont that we can use for this purpose. Loading a file into memory is a snap, but AddMemoryFont accepts an IntPtr and a length. Therefore we need to do fiddle a bit to get the IntPtr from the byte array. Here's a method that loads a font family from a byte array:
// load font family from byte array
public static FontFamily LoadFontFamily(byte[] buffer, out PrivateFontCollection fontCollection) {
// pin array so we can get its address
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try {
var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
fontCollection = new PrivateFontCollection();
fontCollection.AddMemoryFont(ptr, buffer.Length);
return fontCollection.Families[0];
} finally {
// don't forget to unpin the array!
handle.Free();
}
}
As you can see, I'm using GCHandle and UnsafeAddrOfPinnedArrayElement to get the IntPtr to the first element in the array. If you prefer to use unsafe blocks, go ahead, it's even shorter:
// load font family from byte array
public static unsafe FontFamily LoadFontFamilyUnsafe(byte[] buffer, out PrivateFontCollection fontCollection) {
fixed (byte* ptr = buffer) {
fontCollection = new PrivateFontCollection();
fontCollection.AddMemoryFont(new IntPtr(ptr), buffer.Length);
return fontCollection.Families[0];
}
}
For convenience we provide another overload that accepts a stream:
// Load font family from stream
public static FontFamily LoadFontFamily(Stream stream, out PrivateFontCollection fontCollection) {
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
return LoadFontFamily(buffer, out fontCollection);
}
With those methods available we can load a Font for example from an embedded resource:
using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream("Test.TheFont.ttf")) {
PrivateFontCollection fonts;
FontFamily family = LoadFontFamily(s, out fonts);
Font theFont = new Font(family, 20.0f);
//...
}
Note: the documentation of AddMemoryFont have this to say:
To use the memory font, text on a control must be rendered with GDI+. Use the SetCompatibleTextRenderingDefault method, passing true, to set GDI+ rendering on the application, or on individual controls by setting the control's UseCompatibleTextRendering property to true. Some controls cannot be rendered with GDI+.
While I haven't noticed problems when using memory fonts even when compatible text rendering is deactivated (which is preferable), you might experience problems. In this case you have two options:
- Enable compatibility mode using SetCompatibleTextRenderingDefault, which is not really desirable as the new GDI text rendering engine is superior to the GDI+ engine.
- Don't use AddMemoryFont, but extract the font to the temp directory and load it from there using AddFontFile
Update 07/07/2008: It seems that you must not dipose the PrivateFontCollection before you're done with the fonts within it; otherwise your app my crash. I updated the methods above to return the PrivateFontCollection instance. The caller has to dispose the collection after he/she is done using the fonts.
HowTo: generische Listen => Lambda Ausdrücke
03.07.2008 10:32:17
|
Oliver Guhr
Seit der .Net Version 3.5 gibt es für Listen und Arrays eine Reihe neuer Funktionen um die Objekte zu durchsuchen, zu sortieren und zu ordnen. Um ein “Gefühl” für die neuen Funktionen zu bekommen habe ich eine kleine Demoanwendung geschrieben um ein Paar dieser neuen Funktionen auszuprobieren.
Grundlegende Informationen zu den Lambda Ausdrücken finden man hier: http://weblogs.asp.net/scottgu/archive/2007/04/08/new-orcas-language-feature-lambda-expressions.aspx
http://www.outofcoffeeexception.de/2008/04/28/LambdaAusdruumlcke+In+C+30.aspx
List<Person> Employees = PersonManager.GetRandomData(19);
Console.WriteLine("shows all employees");
ViewPersonList(Employees);
Console.ReadLine();
Console.Clear();
Console.WriteLine("shows all Liza's (Where)");
ViewPersonList(Employees.Where(x => x.Firstname == "Liza").ToList());
Console.ReadLine();
Console.Clear();
Console.WriteLine("order's all employees by PersonalId (OrderBy)");
ViewPersonList(Employees.OrderBy(x => x.PersonalId).ToList());
Console.ReadLine();
Console.Clear();
Console.WriteLine("order's all employees by surename and firstname (OrderBy)");
ViewPersonList(Employees.OrderBy(x => x.Surname).ThenBy(x => x.Firstname).ToList());
Console.ReadLine();
Console.Clear();
Console.WriteLine("employee statistics (Min,Max,Sum,Average)");
Console.WriteLine("the youngest employee is:\t" + Employees.Min(x => x.Age) + "\tyears old");
Console.WriteLine("the oldest employee is:\t\t" + Employees.Max(x => x.Age) + "\tyears old");
Console.WriteLine("all employee's are:\t\t" + Employees.Sum(x => x.Age) + "\tyears old");
Console.WriteLine("the average age is:\t\t" + Employees.Average(x => x.Age));
Console.ReadLine();
Console.Clear();
Console.WriteLine("groups's all employees by age(GroupBy)");
Console.WriteLine("order's all groups by age(OrderBy)");
Console.WriteLine("order's all employees in group's by surename(OrderBy)");
IEnumerable<IGrouping<int, Person>> query = Employees.GroupBy(x => x.Age);
foreach (IGrouping<int, Person> AgeGroup in query.OrderBy(x => x.Key ))
{
Console.WriteLine("----------Age:" + AgeGroup.Key + "------------------");
ViewPersonList(AgeGroup.ToList().OrderBy(x => x.Surname).ToList());
}
Console.ReadLine();
Den Beispielcode gibts hier.
ShareThis
Deep Dives zu den VSX-Beispielen erschienen
03.07.2008 00:34:30
|
Rainer Schuster
Ende Juni sind auf den VSX Seiten neue Artikel zu den Beispielen aus dem SDK erschienen. Istvan Novak, der Author von LearnVSXNow, in dem er diese Deep Dives populär gemacht hat, hat im Juni mit Microsoft zusammen gearbeitet um seine Artikel dafür zu erstellen. Heraus gekommen ist wieder einmal eine gute Artikelserie in Form eines Word-Dokumentes, die den Einstieg mit den SDK Beispiele in die Visual Studio Erweiterung gut ergänzt. Ken Levy persönlich - Programm Manager für das Visual Studio - hat es sich nicht nehmen lassen, diese Artikel bereit zu stellen.
DocProject - Editor für MAML
03.07.2008 00:12:20
|
Rainer Schuster
DocProject hat großes vor mit seiner Roadmap. Bei Gelegenheit werde ich eine Übersetzung dieser Roadmap aus dem Blog von Dave Saxton posten. Ich bin sehr gespannt wie das Projekt in einem Jahr aussieht. Ich halte nun laufenden Kontakt mit ihm und werde ihm auch immer wieder Feedback zu DocProject geben.
Am meisten bin ich auf den MAML Editor gespannt. Ein echter WYSIWYG-Editor mit dem es einfach sein wird Konzeptuelle Hilfe zu erstellen. Im ersten Schritt ist es eine WPF-Applikation, die dann später als voll integrierbares VSpackage für das Visual Studio 2008 erscheint. Mich interessiert wie gesagt der MAML Editor brennend. Aber warum denn nur? Gibt es nicht auch noch andere Editoren? Nicht so wirklich. Gute Alternativen, auch kommerzielle, die den Anforderungen entsprechen und sich mit Sandcastle integrieren lassen gibt es nach meinen Recherchen nicht.
SHFB hat einen, der auch gut ist, aber nur 50% WYSIWYG. Wie das geht, fragt ihr euch. Es ist ein XML-Editor und per F3 lässt sich eine Vorschau der Seite rendern. In meinem nächsten Artikel zur Serie "Vom Code zur Dokumentation" der nächste Woche erscheint (hier gehts zu Teil 1.) werde ich über SHFB berichten. DocProject leistet das gleiche, was mit SHFB möglich ist. Eine Referenzdokumentation zu erstellen. Den Vorteil, den DocProject besitzt ist die Integration ins Visual Studio - Sourcecodeverwaltung inklusive. Ich hoffe Anfang kommender Woche eine den ersten Preview zu bekommen und euch hier die Funktionalität zu berichten.
“ORMs für .NET im Vergleich” – Eine kleine Kritik
02.07.2008 23:44:00
|
Sebastian Jancke
Nach Monaten ohne (öffentliche) Reaktionen und einer Diskussionen auf der ALT.NET.DE Mailing-Liste will ich versuchen, meine Kritik am Artikel “Objektrelationale Mapper für .NET im Vergleich” zu formulieren. Der Artikel wurde von Holger Schwichtenberg geschrieben und in dem Magazin dotnetpro (Ausgabe 4/2008) publiziert.
Da dieser Artikel sicherlich auch von Einsteigenden gelesen wird, oder von Menschen ohne Erfahrung speziell mit NHibernate, finde ich es wichtig, dass keine Halb-Wahrheiten stehen bleiben. Dieser Eintrag soll ein Versuch sein, mit einigen Fehlern und Missverständnisse im besagten Artikel aufzuräumen. Die Aufstellung ist sicherlich nicht komplett und ich maße mir auch keinerlei Wertung über den Artikel oder das Magazin oder den Autor an.
(Abschnitt: Henne oder Ei) Beim Forward Mapping machen NHibernate und das ADO.NET Entity Framework bislang nicht mit; sie können eine Datenbank nicht auf Basis von Geschäftsobjekten generieren.
Ich zitiere dazu (frei) aus der NHibernate Dokumentation, Abschnitt 3.5 “Optional configuration properties”: Die Eigenschaft “hibernate.hbm2ddl.auto” bestimmt, ob das DDL Schema automatisch bei Erzeugung der ISessionFactory exportiert werden soll. Mögliche Werte sind unter anderem “create” und “drop”. Mit “create-drop” wird das Datenbank-Schema zunächst erzeugt und beim Schließen der ISessionFactory dann gelöscht. Ich kann jedem nur empfehlen, diese Optionen auszuprobieren. Bisher habe ich sie auf Entwicklungs-Datenbanken erfolgreich nutzen können, um meine Produktivität in diesem Bereich zu steigern. Gleichzeitig halte ich eine Kopie der bisher ausgelieferten Datenbank vor. Mit Werkzeugen wie “Quest Comparison Suite” oder “RedGate SqlCompare” kann ich dann zum Ende einer Iteration die Änderungen automatisiert in ein Script übertragen lassen.
(Abschnitt: Geschäftsobjektklassen) Die Fachwelt spricht dann von Persistance Ignorance oder von Plain Old CLR Objects (POCOs). In diese Kategorie fällt NHibernate. NDO kann POCOs als Unterobjekte eines zu persistierenden Objekts verwenden.
Hierbei geht wohl die eigentliche Bedeutung des Wortes “POCO” verloren. Frei nach Fowler ist ein POCO ein “Plain Old CLR Object”. Also ein Objekt, das sonst keine Abhängigkeiten und Einschränkungen hat. Vererbung ist vielleicht die stärkste Form der Abhängigkeit. Durch Vererbung von einer zentralen Basisklasse meines Persistenz-Frameworks gehe ich somit eine sehr starke Abhängigkeit ein. Das Objekt ist somit kein POCO mehr. Die Begriffe POJO und POCO implizieren damit im Bezug auf Persistenz auch direkt einen Teil des Begriffes “Persistence Ignorance”. NDO ist somit nicht PI-fähig und die Geschäftsobjekte in diesem Zusammenhang sind keine POCOs.
(Abschnitt: Mapping und Treiber) Nicht alle Werkzeuge verwenden im Untergrund ADO.NET. VOA und NHibernate arbeiten mit JDBC (Java Database Connectivity), da es sich um Portierungen aus der Java-Welt handelt.
Dazu möchte ich wieder die NHibernate Dokumentation frei zitieren, im speziellen den Abschnitt 2 “Architecture”: NHibernate kann grundsätzlich auf zwei Arten eingesetzt werden: Entweder die Anwendung stellt NHibernate die entsprechenden ADO.NET Verbindungen zur Verfügung, oder aber NHibernate schirmt die Anwendung von der gesamten Datenbank-Kommunikation ab. Wie man in diesem Fall an der entsprechenden Grafik ("full cream" architecture) sehen kann, liegt auch dann ADO.NET (neben OLE DB und ODBC) zu Grunde. Noch deutlicher ist die Beschreibung des Interfaces ISession: “… kapselt eine ADO.NET Verbindung”.
Abschließend möchte ich noch einige Worte zum Fazit des Autors verlieren. Herr Schwichtenberg legt in seinem Vergleich anscheinend sehr viel Wert auf die Unterstützung mittels grafischer Werkzeuge. Ich kann diese Wahl der Kriterien nicht teilen und auch nicht unterstützen. Für Einsteigende mögen grafische Werkzeuge am Anfang sehr eingängig und leicht zu erlernen sein. Ich bezweifle allerdings, dass die “Dekoration” der Geschäftsobjekte mit Attributen schwieriger ist. Dies ist zwar kein “best practice” – da wir die Persistence Ignorate verlieren – ist aber für den Einstieg akzeptabel.
Schwerer wiegt für mich aber noch das Argument der Skalierbarkeit solcher grafischer Werkzeuge. Dazu reicht meist schon der Versuch, eine “mittelgroße” Anwendung (vielleicht 2-3 Mannmonate Aufwand?) in solch einem grafischen Werkzeug darzustellen. Hierbei scheitert derzeit vor allem der Entity Framework Designer – es werden schlicht alle Objekte auf der gesamten “Leinwand” dargestellt (Scott Allen - Visual Designers Don’t Scale). Auch mit Zoom-Funktion, Gruppierungen und Ausschnitte bleibt immer die Tatsache, das visuelle Werkzeuge in der Regel (bisher) einfach schlecht mit der Größe des Domänen-Modells skalieren.
Technorati Tags:
orm,
nhibernate
ASP.NET/ASP MVP Award
02.07.2008 09:35:40
|
Robert Mühsig
Gestern hat mich folgende freudige Email erreicht:
Sehr geehrte(r) Robert Mühsig,
Herzlichen Glückwunsch! Wir freuen uns, Ihnen den Microsoft® MVP Award 2008 verleihen zu können. Mit dem MVP Award danken wir Ihnen für Ihren Einsatz für die Community, mit dem Sie Tag für Tag dazu beitragen, das Leben der Menschen zu bereichern und die Branche erfolgreicher zu machen. Wir schätzen Ihren außerordentlich bedeutenden Beitrag in den technischen Communities zum Thema Microsoft ASP/ASP.NET im vergangenen Jahr hoch ein.
Ich freu mich, dass meine Bemühungen nicht ganz ungesehen geblieben sind und bedanke mich bei denen, welche mich nominiert haben
Notiz am Rande: Wer nicht weiß, was ein MVP ist, dann einfach mal hier nachschauen.
Weiter geht die Reise in die Tiefen von ASP.NET (MVC
) und co.
ShareThis
Sie wurden mit dem Microsoft MVP Award ausgezeichnet!
01.07.2008 19:34:44
|
Norbert Eder
Sehr geehrte(r) Norbert Eder,
Herzlichen Glückwunsch! Wir freuen uns, Ihnen den Microsoft® MVP Award 2008 verleihen zu können. Mit dem MVP Award danken wir Ihnen für Ihren Einsatz für die Community, mit dem Sie Tag für Tag dazu beitragen, das Leben der Menschen zu bereichern und die Branche erfolgreicher zu machen. Wir schätzen Ihren außerordentlich bedeutenden Beitrag in den technischen Communities zum Thema Microsoft Client App Dev im vergangenen Jahr hoch ein.
Tja, was soll ich sagen. Ich fühle mich geehrt und bedanke mich natürlich bei allen, die mich immer wieder unterstützen, die die Nominierung möglich gemacht haben und sowieso und überhaupt bin ich auch für Weltfrieden.
Mal sehen, ob die sich aufdrängende Frage auch noch irgendwie beantwortet wird ...
Woohoo! Linerider in Silverlight 2
01.07.2008 16:33:00
|
Steffen Ritter
Pünktlich zu Neujahr (das MS-Fiskaljahr endete gestern, am 30. Juni) ist eines der coolsten Onlinespiele in neuer verbesserter Fassung in Silverlight verfügbar:
http://linerider.com/play-line-rider-online

Wer Linerider noch nicht kennt möge sich eine Stunde in die Ecke stellen oder besser umgehend die Website besuchen und loszeichnen…
Ich glaube ich werde heute keine weitere Arbeit mehr erledigen können, muss das schließlich testen :)...
Buchempfehlung: Foundation of Programming (eBook)
01.07.2008 14:52:12
|
Robert Mühsig
Durch Thomas und Alex wurde ich auf dieses sehr interessante kostenlose eBook aufmerksam: Foundation of Programming von Karl Seguin
Auf knapp 80 Seiten werden diese Themen angeschnitten:
- ALT.NET
- Domain Driven Design
- Persistence
- Dependency Injection
- Unit Testing
- Object Relational Mappers
- Basics Memory, Exceptions und Proxies
(auch wenn Thomas und Alex es schon gebloggt haben - sowas sollte doch nicht untergehen
)
ShareThis
Sun.NET bei der .net user group Köln am 08. Juli 2008
01.07.2008 14:49:29
|
Albert Weinert
Das Treffen der .net user Group Köln wird am Dienstag, den 8. Juli 2008 um 19.00 Uhr auf dem Sky Beach Köln, Cäcillienstrasse 32, Köln stattfinden.
Diesmal geht’s rund um Sun.NET
Wie jedes Jahr in den Sommerferien machen wir ein außerordentliches UserTreffen. Wo wir uns treffen, was essen, diskutieren rund um .NET , Softwareentwicklung sowie Ideen wir man bestimmte Herausforderungen im Entwickler Alltag bewältigen kann. Ein intensiver Gedankenaustausch ohne Themenvorgabe und Vorträge.
Eingeladen ist jeder der kommen möchte ob sie/er schon bei einem Usertreffen war oder nicht.
Da wir nun Sommer haben gehen wir diesmal an den Strand. Sonnenstühle und Schirme sind vor Ort, für Sonnencreme muss jeder selber sorgen.
http://www.skybeach.de
Getränke und kleinere Snacks kann man dort auch erwerben.
Parken kann man im darunterliegenden ARAL Parkhaus, mit der Bahn fährt man am besten bis Neumarkt und geht dann Richtung Deutzer Brücke. Den Sky Beach erreicht man vom ARAL Parkhaus mit dem Aufzug in die 6. Etage.
Ab 19 Uhr geht’s los, bitte meldet euch an damit wir entsprechende Stühle mit dem Handtuch reservieren können.
Anmeldung über http://www.dnug-koeln.de/treffen/infos/ oder XING
Technorati-Tags:
Usertreffen,
Köln,
.NET Das Bild ist unter CC von http://www.flickr.com/photos/photophob/

TeamCompanion for Excel - Workitems in Excel bearbeiten
01.07.2008 06:57:09
|
Thomas Schissler
Die Workitems aus dem Team Foundation Server können direkt nach Excel geladen, dort
bearbeitet und wieder auf den TFS gepublished werden. Das hierzu erforderliche Add-In
wird bei der Installation des Team Explorers automatisch mitinstalliert und funktioniert
sehr gut. Ein wenig nervig ist allerdings, wenn man zu einem Workitem weitere Informationen
sehen oder eintragen möchte und die entsprechende Spalte nicht angezeigt wird. Dann
muss man die Spalte erst zur Anzeige auswählen und die Liste aktualisieren. Darüber
hinaus wird die Darstellung in Excel schnell unübersichtlich, wenn man viele Felder
anzeigen lässt.
Wäre es nicht schön, wenn man auch in Excel den gewohnten Detail-Dialog zu einem Workitem
hätte? Genau diese Funktion bietet das kostenfreie Tool Ekobit TeamCompanion for Excel.
Das Workitem kann editiert und gespeichert werden. Die selbe Funktion gibt es übrigens
auch für MS Project.
TeamCompanion
for Excel
TeamCompanion
for Project