Compare commits

..
4 Commits
Author SHA1 Message Date
stefankoelle d36d13b44e seo fixes 2026-09-11 18:18:18 +02:00
stefankoelle 75eb7cc749 hub subdomain is now www 2026-09-11 14:33:19 +02:00
stefankoelle 2e307dcc86 many fixes 2026-09-10 23:34:39 +02:00
stefankoelle cd8a75ec13 fix code blocks 2026-09-10 22:54:41 +02:00
31 changed files with 613 additions and 717 deletions
+3
View File
@@ -0,0 +1,3 @@
---
title: "Autoren"
---
+3
View File
@@ -0,0 +1,3 @@
---
title: "Kategorien"
---
+2 -1
View File
@@ -1,6 +1,7 @@
---
title: "Impressum (§ 5 DDG)"
layout: impressum
description: "Impressum und Kontaktdaten des build broken Blog-Archivs: Stefan Kölle, München."
---
**Stefan Kölle**<br>
@@ -12,4 +13,4 @@ E-Mail: buildbroken@moonweb.org
Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV: Stefan Kölle, Anschrift wie oben.
Das vollständige Impressum und die Datenschutzerklärung für diese Website und das moonweb.org-Netzwerk: [hub.moonweb.org/impressum](https://hub.moonweb.org/impressum)
Das vollständige Impressum und die Datenschutzerklärung für diese Website und das moonweb.org-Netzwerk: [www.moonweb.org/impressum/](https://www.moonweb.org/impressum/)
+3
View File
@@ -0,0 +1,3 @@
---
title: "Beiträge"
---
+2 -2
View File
@@ -24,7 +24,7 @@ Seht ihr ähnliche Tendenzen wie Jeremy in der deutschen Community?
[August 27, 2009 um 7:45 pm](/posts/alt-net-energien/)
Es scheint, dass ALT.NET die FDP der Softwareentwicklungswelt ist: die Opposition von der (gefühlten) Herrschaft vom Microsoft <img src="/images/alt-net-energien/icon_wink.gif" alt="" class="wp-smiley"/> Und als solche, ist seine Rolle unabdingbar!
Es scheint, dass ALT.NET die FDP der Softwareentwicklungswelt ist: die Opposition von der (gefühlten) Herrschaft vom Microsoft <img src="/images/alt-net-energien/icon_wink.gif" alt=";)" class="wp-smiley"/> Und als solche, ist seine Rolle unabdingbar!
Das heißt aber noch lange nicht, dass alles was die (CSU) Microsoft tut BÖSE ist, auch wenn sie jedem Amateur das Programieren ermöglichen. Ich meine, Dreamweaver hatte bereits vor 10 Jahren fertige Codeschnipsel für Datenbankanbindung bereitgestellt aber man musste den NICHT nutzen, man konnte es auch selbst ausprogrammieren. Also anders gesagt: keine zwingt dich, oder?
Eins ist sicher: es gibt den Heiligen Gral der Softwareentwicklung noch nicht. Und das bedeutet, man sollte alles mit Obacht genießen und sich vergewissern, dass der gewählte Weg zu dem bestimmten Projekt und zu dem bestimmten Team passt. Dann kann man auch den richtigen Code schreiben und danach ruhig schlafen, egal ob es um Microsoft-Framework oder um Castle-AddOns geht. Und die Amateur-Coder werden ihrer Arbeit nicht lange verkaufen können, wenn diese unpflegbar und unwartbar ist.
@@ -37,7 +37,7 @@ Seht ihr ähnliche Tendenzen wie Jeremy in der deutschen Community?
Praktisches Beispiel: DataReader, DataSet, TypedDataset, xDataSource, LINQ to SQL oder Entity Framework das alles in nicht einmal 6 Jahren. Jeweils für “the next big thing” verkauft oder gepriesen. Jetzt kann ich hergehen und jedes Mal den aktuellsten Liebling verkaufen und preisen, oder ich greife ganz pragmatisch eben zu dem für mich gerade besten und kapsele es einfach in einem Repository, so dass es beliebig austauschbar ist. Um dahin zu kommen, muss man sich aber ein paar Gedanken rund um seinen Code bzw. die Architektur des selben machen und eben nicht alles ungeprüft nachplappern oder die unsäglichen Beispiele übernehmen. Das versetzt mich dann gleichzeitig auch mal in die Lage zu sagen, dass das Entity Framework eben immer noch scheiße ist, dass die ASP.NET-MVC-Sample-Apps zusammengestümpert sind usw. Und im Übrigen macht es MS selbst auch ein ganzes Stück besser, da sie wohl nirgendwo so schonungsloses Feedback für ihre eigenen Produkte bekommen. Von den “Zusammenklickern” können sie das jedenfalls nicht erwarten.
War das jetzt eigentlich noch die Frage? Egal, ich drücke einfach mal den Absenden-Button <img src="/images/alt-net-energien/icon_wink.gif" alt="" class="wp-smiley"/> .
War das jetzt eigentlich noch die Frage? Egal, ich drücke einfach mal den Absenden-Button <img src="/images/alt-net-energien/icon_wink.gif" alt=";)" class="wp-smiley"/> .
3. **Stefan Kölle**
[August 31, 2009 um 7:28 pm](/posts/alt-net-energien/)
+9 -9
View File
@@ -13,17 +13,17 @@ Anonyme Datentypen sind Klassen, die erst beim Kompilieren durch den Compiler de
Definiert und intstanziert werden sie mit new und einem Objektinitialisierer:
```
var mitarbeiter = new { Name = "Müller", Abteilung = "IT" };
```csharp
var mitarbeiter = new { Name = "Müller", Abteilung = "IT" };
```
var ist der implizite Typ und **nicht** der anonyme Datentyp. Er fungiert hier lediglich als container für die Instanz des anonymen Datentyps, die mit new {} erzeugt wird.
Gibt man keinen Namen für die Properties an, werden die Namen der Properties verwendet, mit denen initialisiert wird.
```
var abteilung = new { Abteilungsname = "IT" };
var mitarbeiter = new { Name = "Müller" , abteilung.Abteilungsname };
```csharp
var abteilung = new { Abteilungsname = "IT" };
var mitarbeiter = new { Name = "Müller" , abteilung.Abteilungsname };
Console .WriteLine(mitarbeiter.Abteilungsname);
```
@@ -33,11 +33,11 @@ Anonyme Datentypen bieten sich an, wenn man Abfragen mit Linq ausführt und entw
Hier ein Beispiel aus einem Castle Monorail Projekt, in dem eine erweiterte Liste an eine vm gegeben wird, ohne eine neue Klasse definieren zu müssen. Es soll dabei eine Liste erstellt werden, in der die Mitarbeiter mit ihrem Horoskop verknüpft werden :
```
public void Action() {
```csharp
public void Action() {
IList < Mitarbeiter > mitarbeiterListe = m_service.GetMitarbeiter();
var mitarbeiterMitHoroskopListe = ( from mitarbeiter in mitarbeiterListe
select new { Mitarbeiter = mitarbeiter,
var mitarbeiterMitHoroskopListe = ( from mitarbeiter in mitarbeiterListe
select new { Mitarbeiter = mitarbeiter,
Horoskop = GetHoroskop(mitarbeiter.Geburtsdatum) });
PropertyBag[ "mitarbeiterMitHoroskopListe" ] = mitarbeiterMitHoroskopListe;
}
+63 -104
View File
@@ -8,137 +8,96 @@ author: "Thomas Christian"
categories: ["Architektur", "Pattern", "Tipps"]
---
Nach dem ich meinen vorherigen Artikel zum Thema [Asynchrone Kommunikation mit dem Async-Pattern](/posts/async/) vorgestellt hatte, hat Ralf Westphal auf seinem Blog [One Man Think Tank Gedanken](http://ralfw.blogspot.com/2010/05/asynchrone-kommunikation-mit-ebcs-statt.html) das Vorgehen zur Implementierung einer asynchronen Kommunikation mit Hilfe von Event-Based-Components vorgestellt, welches eine sehr gute Alternative zum Async-Pattern ist. Seinen Eintrag nehme ich zum Anlass, um meine Beispiel-Implementierung des Async-Pattern zu refaktorisieren, um eine bessere Trennung der Verantwortlichkeiten und somit eine bessere Lesbarkeit zu erreichen.
Nach dem ich meinen vorherigen Artikel zum Thema ["Asynchrone Kommunikation mit dem Async-Pattern"](/posts/async/) vorgestellt hatte, hat Ralf Westphal auf seinem Blog ["One Man Think Tank Gedanken"](http://ralfw.blogspot.com/2010/05/asynchrone-kommunikation-mit-ebcs-statt.html) das Vorgehen zur Implementierung einer asynchronen Kommunikation mit Hilfe von Event-Based-Components vorgestellt, welches eine sehr gute Alternative zum Async-Pattern ist. Seinen Eintrag nehme ich zum Anlass, um meine Beispiel-Implementierung des Async-Pattern zu refaktorisieren, um eine bessere Trennung der Verantwortlichkeiten und somit eine bessere Lesbarkeit zu erreichen.
In das Form wird die Abhängigkeit CalcProxy injected.
In das Form wird die Abhängigkeit "CalcProxy" injected.
```csharp
static void Main()
{
CalcProxy calcProxy = new CalcProxy(new Calculator());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(calcProxy));
CalcProxy calcProxy = new CalcProxy(new Calculator());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(calcProxy));
}
```
Im Gegensatz zur vorherigen Version wird in der Form nun nicht mehr der Calculator direkt erzeugt und verwendet, sondern auf den injekteten CalcProxy zugegriffen.
```csharp
public partial class Form1 : Form {
private readonly ICalcProxy m_calcProxy;
private readonly ICalcProxy m\_calcProxy;
public Form1(ICalcProxy calcProxy) {
InitializeComponent();
m_calcProxy = calcProxy;
m_calcProxy.CalcCompleted += CalculatorCalcCompleted;
}
public Form1(ICalcProxy calcProxy) {
        InitializeComponent();
        m\_calcProxy = calcProxy;
        m\_calcProxy.CalcCompleted += CalculatorCalcCompleted;
    }
private void Run\_Click(object sender, EventArgs e) {
int number;
if (Int32.TryParse(txbEingabe.Text, out number)) {
            m\_calcProxy.CalcAsync(number, number);
        }
    }
void CalculatorCalcCompleted(object sender, CalcEventArgs eventArgs) {
        lblCounter.Text = eventArgs.UserState.ToString();
    }
private void Run_Click(object sender, EventArgs e) {
int number;
if (Int32.TryParse(txbEingabe.Text, out number)) {
m_calcProxy.CalcAsync(number, number);
}
}
void CalculatorCalcCompleted(object sender, CalcEventArgs eventArgs) {
lblCounter.Text = eventArgs.UserState.ToString();
}
}
```
Der CalcProxy wiederum bekommt die Abhängigkeit zum Calculator injected und stellt für die Calculator.Calc-Methode sowohl eine synchrone als auch eine asynchrone Methode zur Verfügung.
```csharp
public class CalcProxy : ICalcProxy {
private readonly ICalculator m_calculator;
public event CalcCompletedEventHandler CalcCompleted;
private AsyncOperation m_asyncOperation;
private bool m_isRunning;
private readonly ICalculator m\_calculator;
public CalcProxy(ICalculator calculator) {
m_calculator = calculator;
}
public event CalcCompletedEventHandler CalcCompleted;
public int Calc(int number) {
return m_calculator.Calc(number);
}
private AsyncOperation m\_asyncOperation;
public void CalcAsync(int number, object userState) {
lock (this) {
if (m_isRunning) {
throw new InvalidOperationException("Diese Operation wird bereits ausgeführt");
}
m_isRunning = true;
m_asyncOperation = AsyncOperationManager.CreateOperation(userState);
ThreadPool.QueueUserWorkItem(ExecuteCalc, number);
}
}
private bool m\_isRunning;
public CalcProxy(ICalculator calculator) {
        m\_calculator = calculator;
    }
public int Calc(int number) {
return m\_calculator.Calc(number);
    }
public void CalcAsync(int number, object userState) {
lock (this) {
if (m\_isRunning) {
throw new InvalidOperationException("Diese Operation wird bereits ausgeführt");
            }
            m\_isRunning = true;
            m\_asyncOperation = AsyncOperationManager.CreateOperation(userState);
ThreadPool.QueueUserWorkItem(ExecuteCalc, number);
        }
    }
private void ExecuteCalc(object state) {
var result = Calc((int)state);
        m\_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result);
    }
private void CalcCompletedSuccessful(object result) {
if (CalcCompleted != null) {
            CalcCompleted(this, new CalcEventArgs(null, false, (int)result, result));
        }
    }
private void ExecuteCalc(object state) {
var result = Calc((int)state);
m_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result);
}
private void CalcCompletedSuccessful(object result) {
if (CalcCompleted != null) {
CalcCompleted(this, new CalcEventArgs(null, false, (int)result, result));
}
}
}
```
Nun enthält der Calculator nur noch die Methode die für den Calculator notwendig ist, nämlich die Calc-Methode.
```csharp
public class Calculator : ICalculator {
public int Calc(int number) {
Thread.Sleep(10000);
return number \* number;
    }
public int Calc(int number) {
Thread.Sleep(10000);
return number * number;
}
}
```
## Kommentare (Archiv)
@@ -155,7 +114,7 @@ return number \* number;
[Mai 6, 2010 um 4:38 pm](/posts/async-refactored/)
[...] This post was mentioned on Twitter by .NET German Bloggers, DeveloperBlogs. DeveloperBlogs said: Asynchrone Kommunikation mit dem Async-Pattern (Refactored): Nach dem ich meinen vorherigen Artikel zum Thema Asy… <http://bit.ly/9eV2kr> [...]
[...] This post was mentioned on Twitter by .NET German Bloggers, DeveloperBlogs. DeveloperBlogs said: Asynchrone Kommunikation mit dem Async-Pattern (Refactored): Nach dem ich meinen vorherigen Artikel zum Thema "Asy… <http://bit.ly/9eV2kr> [...]
3. **[Rainer Hilmer](http://dotnet-forum.de/blogs/rainerhilmer/default.aspx)**
[Juli 24, 2010 um 1:56 pm](/posts/async-refactored/)
+60 -119
View File
@@ -42,25 +42,25 @@ Der Code hinter der UI sieht folgendermaßen aus. Beim instanziieren des Forms,
Bei dem Drücken des „Run“-Buttons, wird die Methode Calculator.CalcAsync(…) aufgerufen.
```
public partial class Form1 : Form {
private ICalculator m_calculator;
public Form1() {
InitializeComponent();
m_calculator = new Calculator ();
m_calculator.CalcCompleted += Calculator_CalcCompleted;
}
```csharp
public partial class Form1 : Form {
private ICalculator m_calculator;
public Form1() {
InitializeComponent();
m_calculator = new Calculator();
m_calculator.CalcCompleted += Calculator_CalcCompleted;
}
private void Run_Click( object sender, EventArgs e) {
int number;
if ( Int32 .TryParse(txbEingabe.Text, out number)) {
m_calculator.CalcAsync(number, number);
}
}
private void Run_Click(object sender, EventArgs e) {
int number;
if (Int32.TryParse(txbEingabe.Text, out number)) {
m_calculator.CalcAsync(number, number);
}
}
void Calculator_CalcCompleted( object sender, CalcEventArgs eventArgs) {
lblCounter.Text = eventArgs.Result.ToString();
}
void Calculator_CalcCompleted(object sender, CalcEventArgs eventArgs) {
lblCounter.Text = eventArgs.Result.ToString();
}
}
```
@@ -68,119 +68,60 @@ Sobald die Berechnung fertig ist, wird das CalcCompleted-Event ausgelöst und so
Der Calculator sieht wie folgt aus:
```
public interface ICalculator {
event Calculator . CalcCompletedEventHandler CalcCompleted;
int Calc( int number);
void CalcAsync( int number, object userState);
```csharp
public interface ICalculator {
event Calculator.CalcCompletedEventHandler CalcCompleted;
int Calc(int number);
void CalcAsync(int number, object userState);
}
public class Calculator : ICalculator {
public delegate void CalcCompletedEventHandler ( object sender, CalcEventArgs eventArgs);
public event CalcCompletedEventHandler CalcCompleted;
private AsyncOperation m_asyncOperation;
private bool m_isRunning;
public class Calculator : ICalculator {
public delegate void CalcCompletedEventHandler(object sender, CalcEventArgs eventArgs);
public event CalcCompletedEventHandler CalcCompleted;
private AsyncOperation m_asyncOperation;
private bool m_isRunning;
public int Calc( int number) {
Thread .Sleep(10000);
return number * number;
public int Calc(int number) {
Thread.Sleep(10000);
return number * number;
}
public void CalcAsync(int number, object userState) {
lock (this) {
if (m_isRunning) {
throw new InvalidOperationException("Diese Operation wird bereits ausgeführt");
}
m_isRunning = true;
m_asyncOperation = AsyncOperationManager.CreateOperation(userState);
ThreadPool.QueueUserWorkItem(ExecuteCalc, number);
}
}
private void ExecuteCalc(object state) {
int result = Calc((int)state);
m_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result);
}
private void CalcCompletedSuccessful(object result) {
if (CalcCompleted != null) {
CalcCompleted(this, new CalcEventArgs(null, false, (int)result, result));
}
}
}
public void CalcAsync( int number, object userState) {
lock ( this ) {
if (m_isRunning) {
throw new InvalidOperationException ( "Diese Operation wird bereits ausgeführt" );
}
m_isRunning = true ;
m_asyncOperation = AsyncOperationManager .CreateOperation(userState);
ThreadPool .QueueUserWorkItem(ExecuteCalc, number);
}
public class CalcEventArgs : AsyncCompletedEventArgs {
public CalcEventArgs(Exception error, bool cancelled, int result, object userState)
: base(error, cancelled, userState) {
Result = result;
}
public int Result { get; private set; }
}
```
        .
        .
        .
```
}
```
Wie man im Interface des Calculator sieht, gibt es eine synchrone und eine asynchrone Methode für die Berechnung. Uns interessiert allerdings nur die asynchrone Methode. Das lock und die Prüfung auf m\_isRunning verhindern lediglich, dass die asynchrone Methode während ihrer Ausführung öfter aufgerufen wird. Das Wesentliche an dieser Methode ist der Aufruf von AsyncOperationManager.CreateOperation, denn dieser Aufruf stellt einen Synchronisationskontext bereit, der die Threads miteinander synchronisiert. Für die eigentliche Berechnung wird sich über den ThreadPool ein neuer Thread besorgt, der sich dann um die Abarbeitung der ExecuteCalc in einem eigenen Thread kümmert.
```
public class Calculator : ICalculator {
```
        .
        .
        .
```
private void ExecuteCalc( object state) {
int result = Calc(( int )state);
m_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result);
}
private void CalcCompletedSuccessful( object result) {
if (CalcCompleted != null ) {
CalcCompleted( this , new CalcEventArgs ( null , false , ( int )result, result));
}
}
}
public class CalcEventArgs : AsyncCompletedEventArgs {
public CalcEventArgs( Exception error, bool cancelled, int result, object userState)
: base (error, cancelled, userState) {
Result = result;
}
public int Result { get ; private set ; }
}
```
Wie man im Interface des Calculator sieht, gibt es eine synchrone und eine asynchrone Methode für die Berechnung. Uns interessiert allerdings nur die asynchrone Methode. Das lock und die Prüfung auf m\_isRunning verhindern lediglich, dass die asynchrone Methode während ihrer Ausführung öfter aufgerufen wird. Das Wesentliche an dieser Methode ist der Aufruf von AsyncOperationManager.CreateOperation, denn dieser Aufruf stellt einen Synchronisationskontext bereit, der die Threads miteinander synchronisiert. Für die eigentliche Berechnung wird sich über den ThreadPool ein neuer Thread besorgt, der sich dann um die Abarbeitung der ExecuteCalc in einem eigenen Thread kümmert.
Sobald die Berechnung abgeschlossen ist, wird der Thread mit dem MainThread über den Aufruf von PostOperationCompleted synchronisiert und das CalcCompleted-Event ausgelöst und das Ergebnis in das Label geschrieben (s. o.).
6 public partial class Form1 : Form {
7 private ICalculator m\_calculator;
8 public Form1() {
9 InitializeComponent();
10 m\_calculator = new Calculator();
11 m\_calculator.CalcCompleted += Calculator\_CalcCompleted;
12 }
13
14 private void Run\_Click(object sender, EventArgs e) {
15 int number;
16 if (Int32.TryParse(txbEingabe.Text, out number)) {
17 m\_calculator.CalcAsync(number, number);
18 }
19 }
20
21 void Calculator\_CalcCompleted(object sender, CalcEventArgs eventArgs) {
22 lblCounter.Text = eventArgs.UserState.ToString();
23 }
24 }
## Kommentare (Archiv)
1. <img src="/images/async/3e7b6cb163ea9cc8549abcb8b5a03e2e.jpeg" width="32" height="32" class="avatar avatar-32 photo" alt="Avatar"/> **[Ralf Westphal](http://ralfw.blogspot.com/)**
+4 -4
View File
@@ -106,21 +106,21 @@ Teil 2 gibt es [hier](/posts/cleancode2/)
Was das KATEGORISCHE Abweisen von Kommentaren betrifft, kann ich wirklich nicht einverstanden sein. Ich kann mir Millionen von Fällen vorstellen, wo ein Name nicht aussagend genug sein kann. aber ich finde auch, dass Namen genau so wichtig sind wie DRY oder The Boy Scout Rule.
Bin schon auf die Fortsetzung gespannt <img src="/images/cleancode1/icon_smile.gif" alt="" class="wp-smiley"/>
Bin schon auf die Fortsetzung gespannt <img src="/images/cleancode1/icon_smile.gif" alt=":-)" class="wp-smiley"/>
2. **[Peter Bucher](http://www.aspnetzone.de/blogs/peterbucher/)**
[August 11, 2009 um 7:45 pm](/posts/cleancode1/)
Salute Andreas
Coole Zusammenfassung, gefällt mir gut <img src="/images/cleancode1/icon_smile.gif" alt="" class="wp-smiley"/> .
Coole Zusammenfassung, gefällt mir gut <img src="/images/cleancode1/icon_smile.gif" alt=":-)" class="wp-smiley"/> .
Ein paar Anmerkungen bzw. Ergänzungen:
Das Buch “Clean Code” ist extra extrem und provokant geschrieben, es soll provozieren.
Man sollte nicht alles 1:1 übernehmen und als bare Münze hinnehmen, sondern nur das und soviel wie es Sinn macht.
Mit den Kommentaren sind nicht die JavaDocs / XmlDocs von C# / VB.NET gemeint. API Dokumentation ist nach wie vor sehr wichtig und hilfreich und sollte benutzt und aktualisiert werden.
Stell dir nur mal ein .NET Framework ohne Intellisense-Kommentare vor <img src="/images/cleancode1/icon_smile.gif" alt="" class="wp-smiley"/> .
Stell dir nur mal ein .NET Framework ohne Intellisense-Kommentare vor <img src="/images/cleancode1/icon_smile.gif" alt=":-)" class="wp-smiley"/> .
Zu dont`t return null / don`t pass null muss ich sagen, das Clean Code auf Java fokusiert ist und das dort anders sein
kann. Man sollte null mit Bedacht und Strategie einsetzen, so eine pauschale Aussage kann ich aber für C# nicht gelten lassen.
@@ -142,7 +142,7 @@ Teil 2 gibt es [hier](/posts/cleancode2/)
Aber müssen wir denn jetzt alle um jeden Preis nach den Empfehlungen des Buches “Clean Code” von Robert C. Martin unseren Code umschreiben? Nein, ich denke, manchmal ist es viel vernünftiger seinem Stil treu zu bleiben. Dennoch kann man dabei funktionellen, sauberen, lesbaren Code schreiben.
Wenn ich lese, dies und das ist “böse” und “verboten”, dann hat das etwas von “Bibel”, wo ich mich dann frage, wer gibt hier wem das Recht, Dinge als “verboten” zu deklarieren.
Also man sollte bei aller Clean-Code Diskussion immer das Ziel nicht aus den Augen verlieren und vor allem auf dem Teppich bleiben. <img src="/images/cleancode1/icon_wink.gif" alt="" class="wp-smiley"/>
Also man sollte bei aller Clean-Code Diskussion immer das Ziel nicht aus den Augen verlieren und vor allem auf dem Teppich bleiben. <img src="/images/cleancode1/icon_wink.gif" alt=";)" class="wp-smiley"/>
Grüße aus dem Frankenland.
+1 -1
View File
@@ -105,7 +105,7 @@ In diesem Kapitel zeigt Uncle Bob eine Liste von Smells und wie man sie beseitig
**Zusammenfassung**
An dieser Stelle ein Dank an mein Entwicklerteam, dass ich das Buch so lange in Beschlag nehmen durfte <img src="/images/cleancode2/icon_smile.gif" alt="" class="wp-smiley"/> (Teil 2 ging irgendwie zäher). Einen großen Dank auch an [Peter Bucher](http://www.aspnetzone.de/blogs/peterbucher/) und [Rene Drescher-Hackel](http://weblog.drescher-hackel.de/) für ihre Ergänzungen bzw. Einwände. Man darf sicherlich nicht alles so ganz ernst nehmen und man kann es auch übertreiben (siehe Pyramiden etc.). Natürlich sollte man nicht blind durch die Welt laufen, sondern auch Dinge hinterfragen, aber man muss sagen, dass die Punkte von [CCD](http://www.clean-code-developer.de/) (das ja auf diesem Buch beruht) durchaus plausibel sind. Ich denke schon, dass [CCD](http://www.clean-code-developer.de/) unerfahrenen aber auch erfahrenen Entwicklern einen SOLIDen Weg zeigt, den man gehen kann und der sogar schon gepflastert ist. Man kann sich natürlich auch mit der Machete durch dichten Dschungel schlagen, was natürlich cool ist <img src="/images/cleancode2/icon_smile.gif" alt="" class="wp-smiley"/> aber halt wesentlich langsamer. Es sei denn man ist Chuck Norris.
An dieser Stelle ein Dank an mein Entwicklerteam, dass ich das Buch so lange in Beschlag nehmen durfte <img src="/images/cleancode2/icon_smile.gif" alt=":-)" class="wp-smiley"/> (Teil 2 ging irgendwie zäher). Einen großen Dank auch an [Peter Bucher](http://www.aspnetzone.de/blogs/peterbucher/) und [Rene Drescher-Hackel](http://weblog.drescher-hackel.de/) für ihre Ergänzungen bzw. Einwände. Man darf sicherlich nicht alles so ganz ernst nehmen und man kann es auch übertreiben (siehe Pyramiden etc.). Natürlich sollte man nicht blind durch die Welt laufen, sondern auch Dinge hinterfragen, aber man muss sagen, dass die Punkte von [CCD](http://www.clean-code-developer.de/) (das ja auf diesem Buch beruht) durchaus plausibel sind. Ich denke schon, dass [CCD](http://www.clean-code-developer.de/) unerfahrenen aber auch erfahrenen Entwicklern einen SOLIDen Weg zeigt, den man gehen kann und der sogar schon gepflastert ist. Man kann sich natürlich auch mit der Machete durch dichten Dschungel schlagen, was natürlich cool ist <img src="/images/cleancode2/icon_smile.gif" alt=":-)" class="wp-smiley"/> aber halt wesentlich langsamer. Es sei denn man ist Chuck Norris.
## Kommentare (Archiv)
+10 -10
View File
@@ -21,12 +21,12 @@ Hierfür habe ich folgende Konfiguration verwendet:
Nach der Bestätigung der Konfiguration sieht das Ergebnis folgendermaßen aus :
```
/// <summary>
/// Summary
/// </summary>
class Program {
static void Main() {
```csharp
/// <summary>
/// Summary
/// </summary>
class Program {
static void Main() {
// Kommentar
Console .WriteLine( "Hello World" );
}
@@ -37,11 +37,11 @@ Console .WriteLine( "Hello World" );
1. **Stefan Kölle**
[Juni 9, 2009 um 12:23](/posts/code-to-html/)
[Juni 9, 2009 um 12:23](/posts/code-to-html/)
Leider kann man den Code vom Blog dann nur schwer wieder in Visual Studio zurueck kopieren.
Leider kann man den Code vom Blog dann nur schwer wieder in Visual Studio zurueck kopieren.
2. **Thomas Christian**
[Juni 10, 2009 um 11:45](/posts/code-to-html/)
[Juni 10, 2009 um 11:45](/posts/code-to-html/)
Wenn man die “Number lines from” weglässt, dann kann man den Code wieder gut aus der Seite kopieren.
Wenn man die “Number lines from” weglässt, dann kann man den Code wieder gut aus der Seite kopieren.
+41 -52
View File
@@ -15,44 +15,41 @@ Erstens muss man die IDE anpassen: im März kommt .NET 4.0 raus und da wird Desi
Jetzt zum Code: Nehmen wir eine ganz einfache Klasse `Bill` deren Objekte mit einem `IRepository` gespeichert bzw. geladen werden.
```
using System.Diagnostics.Contracts;
namespace ContractsPrototyp
```csharp
using System.Diagnostics.Contracts;
namespace ContractsPrototyp
{
public class Bill
public class Bill
{
public int Id { get ; set ; }
public string Number { get ; set ; }
public double Value { get ; set ; }
public int Id { get ; set ; }
public string Number { get ; set ; }
public double Value { get ; set ; }
}
10
11
public interface IRepository
public interface IRepository
{
Bill GetBill( string number);
void SaveBill( Bill bill);
Bill GetBill( string number);
void SaveBill( Bill bill);
}
```
Die Kontrakte kann man in den einzelnen Methoden oder für eine ganze Klasse schreiben (unter dem Attribut `ContractInvariantMethode`) aber ich finde am schönsten, dass man die auch auslagern kann: durch eine gegenseitige Markierung können Kontrakt-Klassen und Interfaces als “Paare” definiert werden:
```
```csharp
[ ContractClass ( typeof ( RepositoryContracts ))]
public interface IRepository
public interface IRepository
{
Bill GetBill( string number);
void SaveBill( Bill bill);
Bill GetBill( string number);
void SaveBill( Bill bill);
}
[ ContractClassFor ( typeof ( IRepository ))]
public class RepositoryContracts : IRepository
public class RepositoryContracts : IRepository
{
public Bill GetBill( string number)
public Bill GetBill( string number)
{
Contract .Requires(! string .IsNullOrEmpty(number));
return null ;
return null ;
}
25
public void SaveBill( Bill bill)
public void SaveBill( Bill bill)
{
Contract .Ensures(bill.Id > 0);
}
@@ -63,43 +60,40 @@ Eine Vorbedingung wird mit `Contract.Requires` und eine Nachbedingung mit `Contr
Die eigentliche Implementierung der Klasse schaut dann so aus:
```
public class Repository : IRepository
```csharp
public class Repository : IRepository
{
public Bill GetBill( string nummer)
public Bill GetBill( string nummer)
{
//Würde das Objekt aus Datenhaltung laden
return new Bill ();
return new Bill ();
}
38
public void SaveBill( Bill bill)
public void SaveBill( Bill bill)
{
//Würde das Objekt speichern und ihm eine Id zuweisen
if (BillIsValid( bill )) bill.Id++;
if (BillIsValid( bill )) bill.Id++;
}
44
private static bool BillIsValid( Bill bill)
private static bool BillIsValid( Bill bill)
{
return ! string .IsNullOrEmpty(bill.Nummer);
return ! string .IsNullOrEmpty(bill.Nummer);
}
}
```
Woher können wir wissen, dass das funktioniert? Es ist einfach, wir schreiben ein Paar Tests dazu!
Woher können wir wissen, dass das funktioniert? Es ist einfach, wir schreiben ein Paar Tests dazu!
Bei Kontraktverletzung wird eine Exception geworfen. Um diese und dadurch die genaue Verletzung überprüfen zu können braucht man etwas Workaround:
```
```csharp
[ TestFixture ]
public class BillTests
public class BillTests
{
private IRepository m_repository;
private string m_message;
60
private IRepository m_repository;
private string m_message;
[ SetUp ]
public void Setup()
public void Setup()
{
m_repository = new Repository ();
m_message = string .Empty;
m_repository = new Repository ();
m_message = string .Empty;
Contract .ContractFailed += ( sender, e ) =>
{
e.SetUnwind();
@@ -110,37 +104,32 @@ m_message = e.Message;
Danach sind die Tests dann einfach:
```
```csharp
[ Test ]
public void Laden_mit_leerer_Nummer_verletzt_Kontrakt()
public void Laden_mit_leerer_Nummer_verletzt_Kontrakt()
{
76
try
{
m_repository.GetBill( null );
m_repository.GetBill( null );
}
catch
{
//Nichts
}
85
Assert .That( m_message, Is .EqualTo( "Precondition failed: !string.IsNullOrEmpty(number)" ) );
Assert .That( m_message, Is .EqualTo( "Precondition failed: !string.IsNullOrEmpty(number)" ) );
}
88
[ Test ]
public void Speichern_Rechnung_ohne_Nummer_verletzt_Kontrakt()
public void Speichern_Rechnung_ohne_Nummer_verletzt_Kontrakt()
{
92
try
{
m_repository.SaveBill( new Bill {Value = 25} );
m_repository.SaveBill( new Bill {Value = 25} );
}
catch
{
//Nichts
}
101
Assert .That( m_message, Is .EqualTo( "Postcondition failed: bill.Id > 0" ) );
Assert .That( m_message, Is .EqualTo( "Postcondition failed: bill.Id > 0" ) );
}
```
+2 -2
View File
@@ -17,7 +17,7 @@ Wikipedia definiert DbC folgendermaßen:
[Stefan Lieser](http://www.lieser-online.de/blog/) hat über das neue Konzept einen sehr guten Artikel in [www.visualstudio1.de](http://www.visualstudio1.de/) geschrieben, mit Begriffsklärung und Anwendungsbeispiele. Kurz zusammengefasst ist die Rede von Folgendem: anstelle, dass man in jeder Methode auf not-null or not-empty usw. prüft, schreibt man die Erwartungen als Code hin. Diese werden von Tools wie z.B. Resharper erkannt und bei fehlerhaften Aufruf der Methode, wird der Entwickler gewarnt. Am besten finde ich, dass man den Kontrakt-Code z.B. für einen Interface in eine separate Klasse schreiben kann und diese wird durch Attribute (`ContractClassFor`) gefunden. Also wird dadurch der eigentliche Code nicht größer.
Das soll nicht bedeuten, dass der Entwickler der Methode die Verantwortung von sich schiebt <img src="/images/design-by-contract/icon_wink.gif" alt="" class="wp-smiley"/> , sondern dass er Bedingungen der “Nutzung” offenlegt, Informationen, die bisher nur durch das Anschauen des Codes oder durch mündliche/schriftliche Mitteilung möglich war. Mit **Spec#** kann man die **Intention-Revealing Interfaces** ganz genau schreiben: mit veröffentlichten und kompilierten Bedingungen.
Das soll nicht bedeuten, dass der Entwickler der Methode die Verantwortung von sich schiebt <img src="/images/design-by-contract/icon_wink.gif" alt=";)" class="wp-smiley"/> , sondern dass er Bedingungen der “Nutzung” offenlegt, Informationen, die bisher nur durch das Anschauen des Codes oder durch mündliche/schriftliche Mitteilung möglich war. Mit **Spec#** kann man die **Intention-Revealing Interfaces** ganz genau schreiben: mit veröffentlichten und kompilierten Bedingungen.
## Kommentare (Archiv)
@@ -37,7 +37,7 @@ Das soll nicht bedeuten, dass der Entwickler der Methode die Verantwortung von s
Hallo Rainer,
danke für den Hinweis, das ist mir schon bekannt. Steht auch in den o.g. Artikel, den ich hier nicht abschreiben wollte, er ist ja nicht von mir <img src="/images/design-by-contract/icon_smile.gif" alt="" class="wp-smiley"/> . Außerdem würde ich ja nie über Code schreiben, den ich selbst nicht ausprobiert habe, also ist es klar, dass es jetzt schon geht.
danke für den Hinweis, das ist mir schon bekannt. Steht auch in den o.g. Artikel, den ich hier nicht abschreiben wollte, er ist ja nicht von mir <img src="/images/design-by-contract/icon_smile.gif" alt=":-)" class="wp-smiley"/> . Außerdem würde ich ja nie über Code schreiben, den ich selbst nicht ausprobiert habe, also ist es klar, dass es jetzt schon geht.
Der andere Grund, warum ich das nicht speziell erwähnt habe, ist, dass es z.Z. zwar möglich ist, das Konzept anzuwenden, aber die Unterstützung seitens VS fehlt, es sei denn, du nutzt TSF.
Mir ging es eigentlich darum, dass es endlich möglich ist, Anforderungen als Code zu implementieren und als solche schriftlich festhalten, verfolgen und auswerten.
@@ -11,7 +11,7 @@ tags: ["Community", "Open Space", "Veranstaltung"]
Wir haben es gerade geschafft, die ganzen Infos vom .NET Open Space Süd zu ordnen, und schon ging es mit der Anmeldung für den nächsten [Open Space in Leipzig](http://netopenspace.de/2009/) los!
Der Andrang war so groß, dass innerhalb von ein paar Tagen die Teilnehmerliste voll wurde.
Nach meiner Erfahrungen in [Ulm](/categories/open-space/), kann ich das voll verstehen, da wird einem das Wissen mit goldenem Löffel serviert <img src="/images/dotnet-openspace-leipzig-2009-buchung/icon_wink.gif" alt="" class="wp-smiley"/> Und man darf auch noch selbst bestimmen, über welches Thema und wie detailliert man erfahren möchte. Und das von den besten Köpfen der Branche, eventuell sogar neben einem Bier, was möchte man/frau mehr ??
Nach meiner Erfahrungen in [Ulm](/categories/open-space/), kann ich das voll verstehen, da wird einem das Wissen mit goldenem Löffel serviert <img src="/images/dotnet-openspace-leipzig-2009-buchung/icon_wink.gif" alt=";)" class="wp-smiley"/> Und man darf auch noch selbst bestimmen, über welches Thema und wie detailliert man erfahren möchte. Und das von den besten Köpfen der Branche, eventuell sogar neben einem Bier, was möchte man/frau mehr ??
Unter dem Titel Agenda steht folgendes:
@@ -21,6 +21,6 @@ Unter dem Titel Agenda steht folgendes:
> \* erachtest du dein Thema als wichtig, nimmst du eine mögliche Diskussion selbst in die Hand und findest interessierte Teilnehmer
> \* die Agenda wird von den Teilnehmenden zum Beginn des .NET Open Space selbst erstellt
Wir haben heute unsere Zimmer reserviert, Betten verteilt <img src="/images/dotnet-openspace-leipzig-2009-buchung/icon_wink.gif" alt="" class="wp-smiley"/> . Mit einem Wort: wir sind bereit!
Wir haben heute unsere Zimmer reserviert, Betten verteilt <img src="/images/dotnet-openspace-leipzig-2009-buchung/icon_wink.gif" alt=";)" class="wp-smiley"/> . Mit einem Wort: wir sind bereit!
Kommst du mit?
@@ -13,7 +13,7 @@ Es ist soweit, morgen geht es los: unsere erste [Open Space Veranstaltung](http:
Wir fahren zu viert hin, alle sehr gespannt und erwartungsvoll, mit sehr viele Fragen in den Taschen.
Ich habe auch noch eine geheime Frage, worauf ich eine Antwort finden hoffe: warum sind außer mir keine Frauen dabei !! <img src="/images/dotnet-openspace-sued-2009-buchung/icon_smile.gif" alt="" class="wp-smiley"/>
Ich habe auch noch eine geheime Frage, worauf ich eine Antwort finden hoffe: warum sind außer mir keine Frauen dabei !! <img src="/images/dotnet-openspace-sued-2009-buchung/icon_smile.gif" alt=":-)" class="wp-smiley"/>
## Kommentare (Archiv)
+4 -4
View File
@@ -14,13 +14,13 @@ Wir sind zu viert früh morgens in München aufgebrochen, um bei unserer ersten
Da wir eine relativ lange Anfahrt hatten, waren fast alle schon da, als wir ankamen. Es waren um die 60 Leute.
Während der Vorstellungsrunde haben wir gemerkt, dass das Open Space nicht nur von Gurus besucht wird, sondern auch von Einsteigern. Wir hatten befürchtet, dass wir dort fehl am Platz sind, da wir relativ neu in der .NET-Community sind und noch relativ wenig Wissen weitergeben können. Aber wir wurden freundlich aufgenommen und können Jedem nur empfehlen, beim nächsten Mal dort hinzugehen. Einen schnelleren Weg, Wissen aufzubauen, gibt es nicht!
Falls sich jemand nicht mehr an uns erinnert, hier ein Bild von uns <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt="" class="wp-smiley"/>
Falls sich jemand nicht mehr an uns erinnert, hier ein Bild von uns <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt=":-)" class="wp-smiley"/>
<img src="/images/dotnet-openspace-sued-2009/16787936.jpg" alt="Foto vom Open Space Süd 2009"/>
(von links nach rechts: [Andreas Seebauer](http://www.xing.com/profile/Andreas_Seebauer2), [Stefan Kölle](http://www.xing.com/profile/Stefan_Koelle), [Thomas Christian](http://www.xing.com/profile/Thomas_Christian9), [Christina Hirth](http://www.xing.com/profile/Christina_Hirth2))
Es gab zwar keine vorher festgelegten Themen, aber diese wurden vor Ort vorgeschlagen und danach demokratisch bestimmt. Es wurden sehr interessante Bereiche besprochen, aber leider konnte man nicht in jeden Open Space gleichzeitig. <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt="" class="wp-smiley"/>
Es gab zwar keine vorher festgelegten Themen, aber diese wurden vor Ort vorgeschlagen und danach demokratisch bestimmt. Es wurden sehr interessante Bereiche besprochen, aber leider konnte man nicht in jeden Open Space gleichzeitig. <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Für uns am interessantesten waren:
MVC, DDD, IoC und DI (danke an [Albert](http://der-albert.com/), [Markus](http://mortslikeus.blogspot.com/), [Thomas](http://blog.thomasbandt.de/), [Peter](http://www.aspnetzone.de/blogs/peterbucher/), [Stefan](http://lieser-online.de/) und [Sergey](http://shishkin.org/))
@@ -34,9 +34,9 @@ Am Sonntag hat uns besonders die Clean-Code-Development-Runde gefallen. Wir sind
Die Organisation war einwandfrei. Das Essen war lecker und Getränke waren massenhaft vorhanden. Danke an [Thomas](http://www.artiso.com/problog/) und [Alex](http://blog.alexonasp.net/).
Raum 5 war auch das absolute Highlight. Es war immer sehr entspannend da oben.
Leider haben wir die Party verpasst, da wir nicht dort übernachtet haben. Wir hätten zu gerne Alberts Bauchtanz gesehen. <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt="" class="wp-smiley"/>
Leider haben wir die Party verpasst, da wir nicht dort übernachtet haben. Wir hätten zu gerne Alberts Bauchtanz gesehen. <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Beim Gewinnspiel hat natürlich Stefan den Hauptpreis (eine Telerik-Lizenz) gewonnen. <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt="" class="wp-smiley"/>
Beim Gewinnspiel hat natürlich Stefan den Hauptpreis (eine Telerik-Lizenz) gewonnen. <img src="/images/dotnet-openspace-sued-2009/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Auch einen Tag nach dem .NET Open Space Süd sind wir immer noch voller Begeisterung und müssen das Erlernte erst einmal verarbeiten. Für uns ist jetzt schon klar, dass wir unbedingt beim nächsten Mal wieder dabei sein wollen.
+2 -2
View File
@@ -52,7 +52,7 @@ Anhang:
[Februar 16, 2010 um 9:19 am](/posts/mvp-winforms/)
Hallo,
ich finde die Anwendung sehr schön und es sind die Eine oder Andere Anregung für mich auch enthalten <img src="/images/mvp-winforms/icon_smile.gif" alt="" class="wp-smiley"/> .
ich finde die Anwendung sehr schön und es sind die Eine oder Andere Anregung für mich auch enthalten <img src="/images/mvp-winforms/icon_smile.gif" alt=":-)" class="wp-smiley"/> .
Die Konfiguration über Xml würde ich vielleicht noch versuchen zu eliminieren, es wird ja schönes Benennungsmuster eingehalten (CoC).
Ich habe es zwar noch nicht laufen lassen, es sah aber im TimelinePresenter so aus, als wenn noch ein versehentliches Verlassen verhindert werden könnte…
Viele Grüße,
@@ -79,7 +79,7 @@ Anhang:
Zu dem versehentlichen Verlassen:
Ich habe vor Kurzem etwas über das “graceful exit” einer Anwendung gelesen. Deine Anwendung hatte ich nicht gestartet, sondern mir nur den Quellcode quergelesen. Dabei ist mir aufgefallen, dass nur ein Application.Exit beim Close ausgeführt wird, der Anwender erhält keine Chance doch in der Anwendung zu bleiben. Eventuell habe ich es auch einfach nicht gesehen (habe es ja auch nicht laufen lassen)…
Soll kein Mäkeln an Kleinigkeiten sein, sondern eher ein Beispiel dafür, dass mir die Anwendung recht gut gefällt. <img src="/images/mvp-winforms/icon_wink.gif" alt="" class="wp-smiley"/>
Soll kein Mäkeln an Kleinigkeiten sein, sondern eher ein Beispiel dafür, dass mir die Anwendung recht gut gefällt. <img src="/images/mvp-winforms/icon_wink.gif" alt=";)" class="wp-smiley"/>
Viele Grüße,
Jan
+52 -60
View File
@@ -9,46 +9,44 @@ categories: ["How-To"]
tags: ["Architektur", "C#", "HowTo", "Plug-In"]
---
In meinem ersten Blog-Post möchte ich euch in vereinfachter Form meine Umsetzung des Plug-In-Patterns vorstellen.
In meinem ersten Blog-Post möchte ich euch in vereinfachter Form meine Umsetzung des Plug-In-Patterns vorstellen.
Voraussetzungen für dieses Pattern sind:
* eine Host-Applikation die das Plug-In laden möchte
* ein Plug-In welches es zu laden gilt
* und die Schnittstellen Plugin und IHost
Zu allererst brauchen wir eine gemeinsame Schnittstelle, die sowohl vom Plug-In als auch vom Host (die Applikation welche das Plug-In verwenden soll) benutzt werden soll. Über diese Schnittstelle kommuniziert der Host mit dem Plug-In.
Zu allererst brauchen wir eine gemeinsame Schnittstelle, die sowohl vom Plug-In als auch vom Host (die Applikation welche das Plug-In verwenden soll) benutzt werden soll. Über diese Schnittstelle kommuniziert der Host mit dem Plug-In.
Es würde sich das Interface IPlugIn anbieten. In meinem Fall verwende ich allerdings kein Interface sondern eine abstrakte Klasse, da ich bereits Logik direkt in die Schnittstelle implementieren möchte. In meinem Fall heißt die abstrakte Klasse einfach nur Plugin. Im weiteren Verlauf werde ich zu meiner abstrakten Klasse Schnittstelle sagen.
```
public abstract class Plugin {
private IHost m_host;
public Plugin( string name) {
```csharp
public abstract class Plugin {
private IHost m_host;
public Plugin( string name) {
Name = name;
}
6
public string Name { get ; set ; }
public string Author { get ; set ; }
public string Version { get ; set ; }
public bool IsRegistered { get ; private set ; }
11
/// <summary>
/// Setzt oder gibt die Host-Application.
/// </summary>
/// <value> Host-Application. </value>
public IHost Host {
get { return m_host; }
set {
if ( value != null ) {
if (m_host == null ) {
m_host = value ;
if (m_host.Register( this )) {
IsRegistered = true ;
public string Name { get ; set ; }
public string Author { get ; set ; }
public string Version { get ; set ; }
public bool IsRegistered { get ; private set ; }
/// <summary>
/// Setzt oder gibt die Host-Application.
/// </summary>
/// <value> Host-Application. </value>
public IHost Host {
get { return m_host; }
set {
if ( value != null ) {
if (m_host == null ) {
m_host = value ;
if (m_host.Register( this )) {
IsRegistered = true ;
}
}
} else {
if (m_host.Unregister( this )) {
m_host = value ;
IsRegistered = false ;
} else {
if (m_host.Unregister( this )) {
m_host = value ;
IsRegistered = false ;
}
}
}
@@ -56,17 +54,17 @@ IsRegistered = false ;
}
```
Ich glaube, der Aufbau der Schnittstelle Plugin sollte bis auf die Property “Host” klar sein. Wie man sieht, benötigt man bei diesem Pattern zusätzlich zur Schnittstelle Plugin noch das IHost-Interface.
Ich glaube, der Aufbau der Schnittstelle Plugin sollte bis auf die Property “Host” klar sein. Wie man sieht, benötigt man bei diesem Pattern zusätzlich zur Schnittstelle Plugin noch das IHost-Interface.
Dieses Interface ist direkter Bestandteil der Schnittstelle Plugin. Nun kann man sich natürlich die Frage stellen, warum das Plug-In den Host kennen muss und somit von diesem abhängig ist.
Zum einen ist es in der Regel so, dass ein Plug-In für nur einen Host entwickelt wird und zum anderen wollte ich dem Host gewisse Richtlinien zum Registrieren und Lösen des Plug-Ins vorgeben.
Das Interface IHost sieht folgendermaßen aus:
```
public interface IHost {
bool Register(Plugin plugin);
bool Unregister(Plugin plugin);
```csharp
public interface IHost {
bool Register(Plugin plugin);
bool Unregister(Plugin plugin);
}
```
@@ -74,51 +72,45 @@ Dieses Interface muss von der Host-Applikation implementiert werden, damit sich
Eine vereinfachte Darstellung der Implementierung des IHost -Interfaces sieht folgendermaßen aus:
```
public class HostApplication : IHost {
private List<Plugin> m_pluginList;
3
```csharp
public class HostApplication : IHost {
private List<Plugin> m_pluginList;
//Konstruktor
public HostApplication() {
m_pluginList = new List<Plugin>();
7
public HostApplication() {
m_pluginList = new List<Plugin>();
//Lädt alle verfügbaren Plug-Ins zb. aus einem Verzeichnis
List<Plugin> plugins = GetPlugins();
foreach (Plugin plugin in plugins) {
foreach (Plugin plugin in plugins) {
//ruft implizit die IHost.Register-Methode auf
plugin.Host = this ;
plugin.Host = this ;
}
}
15
public void ShowPlugins() {
foreach (Plugin plugin in m_pluginList) {
public void ShowPlugins() {
foreach (Plugin plugin in m_pluginList) {
//Ausgabe der Namen aller am Host registrierten Plug-Ins
Console.WriteLine(plugin.Name);
}
}
22
public void UnloadPlugins() {
foreach (Plugin plugin in m_pluginList) {
public void UnloadPlugins() {
foreach (Plugin plugin in m_pluginList) {
//ruft implizit die IHost.Unregister-Methode auf
plugin.Host = null ;
plugin.Host = null ;
}
}
29
#region IHost-Implementierung
public bool Register(Plugin plugin) {
if (!m_pluginList.Contains(plugin)) {
#region IHost-Implementierung
public bool Register(Plugin plugin) {
if (!m_pluginList.Contains(plugin)) {
m_pluginList.Add(plugin);
return true ;
return true ;
}
return false ;
return false ;
}
38
public bool Unregister(Plugin plugin) {
if (m_pluginList.Contains(plugin)) {
public bool Unregister(Plugin plugin) {
if (m_pluginList.Contains(plugin)) {
m_pluginList.Remove(plugin);
return true ;
return true ;
}
return false ;
return false ;
}
#endregion
}
+1 -1
View File
@@ -63,6 +63,6 @@ Vielen Dank
Hallo Sebastian,
das könnte durchaus der Fall gewesen sein. <img src="/images/prio-conference-2009/icon_wink.gif" alt="" class="wp-smiley"/>
das könnte durchaus der Fall gewesen sein. <img src="/images/prio-conference-2009/icon_wink.gif" alt=";)" class="wp-smiley"/>
lg Tom
+7 -7
View File
@@ -9,7 +9,7 @@ categories: ["Clean Code Developing"]
tags: ["Clean Code"]
---
Obwohl wir [Jeff Atwood](http://www.codinghorror.com/blog/) schon vor Monaten von unseren Bloggerliste gestrichen haben, ich habe ihn noch nicht aus meinem Googlereader entfernt. Er ist ein Typ, der liebt viel Wirbel zu verursachen ([Scott Hanselmann](http://www.hanselman.com/blog/) nennt ihn aus den Wörtern *friend* und *enemy* “my friendemy” <img src="/images/programmierer/icon_wink.gif" alt="" class="wp-smiley"/> ) aber manchmal trifft er den Nagel auf den Kopf. So auch in seinem letzten Artikel [“The Non-Programming Programmer”](http://www.codinghorror.com/blog/2010/02/the-nonprogramming-programmer.html).
Obwohl wir [Jeff Atwood](http://www.codinghorror.com/blog/) schon vor Monaten von unseren Bloggerliste gestrichen haben, ich habe ihn noch nicht aus meinem Googlereader entfernt. Er ist ein Typ, der liebt viel Wirbel zu verursachen ([Scott Hanselmann](http://www.hanselman.com/blog/) nennt ihn aus den Wörtern *friend* und *enemy* “my friendemy” <img src="/images/programmierer/icon_wink.gif" alt=";)" class="wp-smiley"/> ) aber manchmal trifft er den Nagel auf den Kopf. So auch in seinem letzten Artikel [“The Non-Programming Programmer”](http://www.codinghorror.com/blog/2010/02/the-nonprogramming-programmer.html).
>
> I wrote that article in 2007, and I am stunned, but not entirely surprised, to hear that three years later “the vast majority” of so-called programmers who apply for a programming job interview are unable to write the smallest of programs. To be clear, hard is a relative term — were not talking about complicated, Google-style graduate computer science interview problems. This is extremely simple stuff were asking candidates to do. And they cant. **Its the equivalent of attempting to hire a truck driver and finding out that 90 percent of the job applicants cant find the gas pedal or the gear shift.**
@@ -41,7 +41,7 @@ Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? W
Hallo Christina, ich überlege gerade, was schlimmer ist: ein Programmierer, der das Geschäft irgendwann von der Pieke auf gelernt jetzt aber meint, dass das fürs ganze Programmiererlebe reicht oder ein Quereinsteiger, dem Basics fehlen dafür aber gewillt ist, sich permanent zu verbessern… Wie so oft, hängt es vermutlich davon ab, was man erreichen will / muss, wenn man die betreffende Person einstellt.
Code-Reviews sind hier bestimmt kein schlechter Ansatz, aber sie sollten vielleicht nicht das einzige Einstellungskriterium sein. Auf jeden Fall sollte der Code aber gemeinsam reviewed werden, denn vielleicht hat ja auch der Reviewer eine verkehrte Sicht <img src="/images/programmierer/icon_wink.gif" alt="" class="wp-smiley"/>
Code-Reviews sind hier bestimmt kein schlechter Ansatz, aber sie sollten vielleicht nicht das einzige Einstellungskriterium sein. Auf jeden Fall sollte der Code aber gemeinsam reviewed werden, denn vielleicht hat ja auch der Reviewer eine verkehrte Sicht <img src="/images/programmierer/icon_wink.gif" alt=";)" class="wp-smiley"/>
Ich habe in den letzten Jahren in anderen Bereichen (Maschinen-/Anlagenbau) festgestellt, dass man den perfekten Mitarbeiter nicht bekommt. Viel wichtiger ist es imho, die Stärken und Schwächen des einzelnen zu kennen (das ist ein mühsamer Prozess) und daraus ein schlagkräftiges Team zu formen.
3. **Christina Hirth**
@@ -56,7 +56,7 @@ Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? W
Ich finde deine Idee mit dem Dojo in der Probezeit, nehme ich an sehr gut, die Teilnahme oder Nicht-Teilnahme an so eine kleine Herausforderung kann sehr vieles aussagen.
Du bist in der Position, in der dir die deutschen Gepflogenheiten bei der Zusammenstellung eines Team egal sein kann, ich dagegen sitze an der anderen Seite des Tisches <img src="/images/programmierer/icon_wink.gif" alt="" class="wp-smiley"/> Ich wünschte mir, dass mehr Leute so denken würden wie du. Vielleicht könnte das auch zu einer Aufgabe der CCD-Bewegung werden.
Du bist in der Position, in der dir die deutschen Gepflogenheiten bei der Zusammenstellung eines Team egal sein kann, ich dagegen sitze an der anderen Seite des Tisches <img src="/images/programmierer/icon_wink.gif" alt=";)" class="wp-smiley"/> Ich wünschte mir, dass mehr Leute so denken würden wie du. Vielleicht könnte das auch zu einer Aufgabe der CCD-Bewegung werden.
Christina
4. **Christina Hirth**
@@ -66,7 +66,7 @@ Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? W
Hallo Alex,
Ich bin genau deiner Meinung, es hängt fast alles davon ab, wozu die Position ausgeschrieben wurde. Die Frage ist allerdings, ob da noch Platz für Nachbesserung vorhanden ist, wo eventuell kein Wille ist. Dann bin ich mehr für den Quereinsteiger ich bin auch einer wenn der Grund für die Neuorientierung eine falsche erste Wahl war und nicht “ich habe mir mal eine Homepage gebastelt, also bin ich Programmierer”
Was den perfekten Mitarbeiter betrifft: hoffentlich gibt es ihn noch nicht, das würde das Leben der anderen im Team für immer vermiesen <img src="/images/programmierer/icon_wink.gif" alt="" class="wp-smiley"/>
Was den perfekten Mitarbeiter betrifft: hoffentlich gibt es ihn noch nicht, das würde das Leben der anderen im Team für immer vermiesen <img src="/images/programmierer/icon_wink.gif" alt=";)" class="wp-smiley"/>
5. **[Golo Roden](http://www.des-eisbaeren-blog.de/)**
[März 14, 2010 um 7:28 pm](/posts/programmierer/)
@@ -114,7 +114,7 @@ Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? W
Hallo André,
endlich jemand aus der anderen Mannschaft <img src="/images/programmierer/icon_smile.gif" alt="" class="wp-smiley"/>
endlich jemand aus der anderen Mannschaft <img src="/images/programmierer/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Deine Vorgehensweise finde ich sehr effizient, aber hätte es nicht passieren können, dass der Bewerbungsstress dein Bild über den Kandidaten verzehren könnte? Oder wusste er schon vor vornherein, dass die Aufgabe unvollständig ist?
@@ -126,9 +126,9 @@ Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? W
[März 15, 2010 um 1:54 pm](/posts/programmierer/)
Dass der “Bewerbungsstreß” meine Wahrnehmung verzerrt haben könnte, kann ich nicht aussließen. Da die Aufgaben allerdings meist recht einfach waren und ich auf eine angenehme Gesprächsatmosphäre geachtet habe, hoffe bzw. glaube ich nicht, dass jemand so viel Streß hatte, dass es zu einer vollkommen falschen Wahrnehmung hätte führen können. Außerdem sollte man auch bedenkend, dass es bei der späteren Ausübung des Jobs immer wieder Situationen geben kann, in denen man unter Streß / Druck arbeiten muss. Daher simulierte die Situation vielleicht sogar ein wenig eine realistische Arbeitssituation <img src="/images/programmierer/icon_wink.gif" alt="" class="wp-smiley"/>
Dass der “Bewerbungsstreß” meine Wahrnehmung verzerrt haben könnte, kann ich nicht aussließen. Da die Aufgaben allerdings meist recht einfach waren und ich auf eine angenehme Gesprächsatmosphäre geachtet habe, hoffe bzw. glaube ich nicht, dass jemand so viel Streß hatte, dass es zu einer vollkommen falschen Wahrnehmung hätte führen können. Außerdem sollte man auch bedenkend, dass es bei der späteren Ausübung des Jobs immer wieder Situationen geben kann, in denen man unter Streß / Druck arbeiten muss. Daher simulierte die Situation vielleicht sogar ein wenig eine realistische Arbeitssituation <img src="/images/programmierer/icon_wink.gif" alt=";)" class="wp-smiley"/>
Zur Frage ob die Kandidaten vorher wussten, was sie erwartet: Wenn sie gefragt haben dann schon <img src="/images/programmierer/icon_wink.gif" alt="" class="wp-smiley"/>
Zur Frage ob die Kandidaten vorher wussten, was sie erwartet: Wenn sie gefragt haben dann schon <img src="/images/programmierer/icon_wink.gif" alt=";)" class="wp-smiley"/>
Auf Nachfrage habe ich immer sehr ausführlich beschrieben wie wir vorgehen werden. Wer nicht gefragt hat, der hat halt Pech gehabt. Ich denke auch, dass es schon eine Menge über den Bewerber aussagt, ob er sich nach Erhalt einer Einladung telefonisch meldet um ein paar Fragen vorab zu klären, oder nicht.
11. **[KW12: Seitwert, Giana Sisters, Dumm 3.0, UML und mehr - Der Softwareentwickler Blog](http://www.der-softwareentwickler-blog.de/2010/03/26/kw12-giana-sisters-dumm-3-0-uml-visual-studio-2010-und-mehr/)**
+1 -1
View File
@@ -16,5 +16,5 @@ Ich lese gerade das Buch [Agile Principles, Patterns and Practices in C#](http:/
>
> Das Ziel von Refaktorisierung ist die Bereinigung des Codes, jeden Tag, jede Stunde, jede Minute. Wir wollen nicht den Code versauen. Wir wollen nicht ständig gezwungen zu sein, schmutzigen Bits und Bytes sauber zu schrubben. Wir wollen jeder Zeit in der Lage zu sein, unser System mühelos zu erweitern und zu verändern. Der wichtigste Faktor, der uns das möglich macht, ist die Sauberkeit des Codes.
Das Buch habe zwar noch nicht ganz durch, aber was ich bis jetzt gelesen habe, kann ich nur unterschreiben. Es beinhaltet die Prinzipien, wie man in einem agilen Team sauberen Code plant und Mustern und Beispiele, wie man ihn schreibt. Und wie ihr ja sieht, ist es in eine ganz tolle Art geschrieben <img src="/images/refaktorisierung/icon_smile.gif" alt="" class="wp-smiley"/>
Das Buch habe zwar noch nicht ganz durch, aber was ich bis jetzt gelesen habe, kann ich nur unterschreiben. Es beinhaltet die Prinzipien, wie man in einem agilen Team sauberen Code plant und Mustern und Beispiele, wie man ihn schreibt. Und wie ihr ja sieht, ist es in eine ganz tolle Art geschrieben <img src="/images/refaktorisierung/icon_smile.gif" alt=":-)" class="wp-smiley"/>
+143 -153
View File
@@ -19,55 +19,53 @@ Für dieses Beispiel habe ich eine kleine Konsolen-Anwendung geschrieben (TestAp
**TestClass.cs**
```
using System.Security.Cryptography;
using System.Text;
namespace TestProject {
class TestClass {
public string Name { get ; set ; }
public string Surname { get ; set ; }
private string Password { get ; set ; }
public string GetFullName() {
return string .Format( "{0} {1}" , Name ?? string .Empty, Surname ?? string .Empty).Trim();
```csharp
using System.Security.Cryptography;
using System.Text;
namespace TestProject {
class TestClass {
public string Name { get ; set ; }
public string Surname { get ; set ; }
private string Password { get ; set ; }
public string GetFullName() {
return string .Format( "{0} {1}" , Name ?? string .Empty, Surname ?? string .Empty).Trim();
}
public void SetPasswort( string password){
public void SetPasswort( string password){
Password = password;
}
private static string CalcPassword( string password){
MD5 md5 = MD5 .Create();
private static string CalcPassword( string password){
MD5 md5 = MD5 .Create();
byte [] data = md5.ComputeHash( Encoding .Default.GetBytes(password));
StringBuilder hashPassword = new StringBuilder ();
foreach ( byte b in data){
StringBuilder hashPassword = new StringBuilder ();
foreach ( byte b in data){
hashPassword.Append(b);
}
return hashPassword.ToString();
return hashPassword.ToString();
}
public static string DoSomething( string value) {
public static string DoSomething( string value) {
// do something
return value;
return value;
}
}
```
**SecondTestClass.cs**
```
namespace TestProject {
public class SecondTestClass {
public int Addition( int a, int b) {
return a + b;
```csharp
namespace TestProject {
public class SecondTestClass {
public int Addition( int a, int b) {
return a + b;
}
public int Multiply( int a, int b) {
return a * b;
public int Multiply( int a, int b) {
return a * b;
}
public double Divide( int a, int b) {
if (b == 0) return 0;
return a / b;
public double Divide( int a, int b) {
if (b == 0) return 0;
return a / b;
}
}
}
.
.
```
## Auslesen von Assembly- Type-Informationen
@@ -76,33 +74,32 @@ Mit der Methode Thread.GetDomain().GetAssemblies() bekommt man alle referenziert
Um sich nun alle Assemblies der aktuellen Domäne anzeigen zu lassen, könnte man folgendes schreiben:
```
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTest Class ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
```csharp
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTest Class ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
Console .WriteLine( string .Format( "-- {0} ver.: {1}" , assembly.GetName().Name
, assembly.GetName().Version));
, assembly.GetName().Version));
}
Console .ReadLine();
}
```
.
Nun könnte man noch alle zur Verfügung stehenden Typen(Klassen) aus der TestProject-Assembly anzeigen.
```
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
```csharp
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
Console .WriteLine( string .Format( "-- {0} ver.: {1}" , assembly.GetName().Name
, assembly.GetName().Version));
if (assembly.GetName().Name.Equals( "TestProject" )) {
, assembly.GetName().Version));
if (assembly.GetName().Name.Equals( "TestProject" )) {
Type [] types = assembly.GetTypes();
foreach ( var type in types) {
Console .WriteLine( " ++{0}" , type.Name);
foreach ( var type in types) {
Console .WriteLine( " ++{0}" , type.Name);
}
}
}
@@ -110,24 +107,23 @@ Console .ReadLine();
}
```
.
Wenn man nun allerdings Informationen eines bestimmten Types haben möchte, so werden diese Informationen aus den Metadaten des Moduls geladen, in dem sich der Type befindet. Um zum Beispiel alle öffentlichen Methoden eines Types anzuzeigen, könnte man folgendes schreiben:
```
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
```csharp
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
Console .WriteLine( string .Format( "-- {0} ver.: {1}" , assembly.GetName().Name
, assembly.GetName().Version));
if (assembly.GetName().Name.Equals( "TestProject" )) {
, assembly.GetName().Version));
if (assembly.GetName().Name.Equals( "TestProject" )) {
Type [] types = assembly.GetTypes();
foreach ( var type in types) {
Console .WriteLine( " ++ {0}" , type.Name);
foreach ( var type in types) {
Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods();
foreach ( var info in methodInfos) {
Console .WriteLine( " ++ {0}" , info.Name);
foreach ( var info in methodInfos) {
Console .WriteLine( " ++ {0}" , info.Name);
}
}
}
@@ -136,11 +132,10 @@ Console .ReadLine();
}
```
.
Es fällt auf, dass auch die Methoden ToString, Equals, GetHashCode und GetType in beiden Klassen angezeigt werden, obwohl diese in den Klassen überhaupt nicht deklariert wurden. Das liegt daran, dass alle Typen von object ableiten und object diese Methoden implementiert. Was noch auffällt ist, dass nur öffentliche Methoden angezeigt werden. Möchte man sich auch die privaten Methoden anzeigen lassen, so muss man der Methode type.GetMethods Parameter übergeben. Diese Methode verlangt BindingFlags, die man mit dem Bitweise ODER-Operator (|) miteinander verknüpfen kann. Um sich nun öffentliche und private Methoden anzeigen zu lassen, muss der Aufruf der Methode type.GetMethods folgendermaßen lauten:
```
```csharp
MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
```
@@ -148,81 +143,80 @@ Dieser Aufruf gibt an, dass man alle öffentlichen und nicht-öffentlichen Insta
Das ganze könnte man natürlich noch in der Form weiter treiben, dass man sich die kompletten Methodensignaturen mit Modifizierer, Schlüsselwort und Rückgabewert anzeigen lässt:
```
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
```csharp
static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
foreach ( var assembly in assemblies) {
Console .WriteLine( string .Format( "-- {0} ver.: {1}" , assembly.GetName().Name, assembly.GetName().Version));
if (assembly.GetName().Name.Equals( "TestProject" )) {
if (assembly.GetName().Name.Equals( "TestProject" )) {
Type [] types = assembly.GetTypes();
foreach ( var type in types) {
Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods( BindingFlags .Public | BindingFlags .NonPublic
| BindingFlags .Instance
| BindingFlags .Static);
foreach ( var info in methodInfos) {
string modifier = GetModifier(info);
string keyword = GetKeyword(info);
string parameters = GetParameters(info);
if (! string .IsNullOrEmpty(keyword)) {
keyword = string .Format( "{0} " , keyword);
foreach ( var type in types) {
Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods( BindingFlags .Public | BindingFlags .NonPublic
| BindingFlags .Instance
| BindingFlags .Static);
foreach ( var info in methodInfos) {
string modifier = GetModifier(info);
string keyword = GetKeyword(info);
string parameters = GetParameters(info);
if (! string .IsNullOrEmpty(keyword)) {
keyword = string .Format( "{0} " , keyword);
}
Console .WriteLine( " ++ {0} {1}{2}{3}({4})" , modifier, keyword
, info.ReturnParameter, info.Name, parameters);
Console .WriteLine( " ++ {0} {1}{2}{3}({4})" , modifier, keyword
, info.ReturnParameter, info.Name, parameters);
}
}
}
}
Console .ReadLine();
}
private static string GetModifier( MethodInfo info) {
if (info.IsPublic) return "public" ;
return "private" ;
private static string GetModifier( MethodInfo info) {
if (info.IsPublic) return "public" ;
return "private" ;
}
private static string GetKeyword( MethodInfo info) {
if (info.IsStatic) return "static" ;
if (info.IsVirtual) return "virtual" ;
return string .Empty;
private static string GetKeyword( MethodInfo info) {
if (info.IsStatic) return "static" ;
if (info.IsVirtual) return "virtual" ;
return string .Empty;
}
private static string GetParameters( MethodInfo info) {
StringBuilder parameters = new StringBuilder ();
private static string GetParameters( MethodInfo info) {
StringBuilder parameters = new StringBuilder ();
ParameterInfo [] parameterInfos = info.GetParameters();
if (parameterInfos != null ) {
foreach ( ParameterInfo parameterInfo in parameterInfos) {
if (! string .IsNullOrEmpty(parameters.ToString())) {
if (parameterInfos != null ) {
foreach ( ParameterInfo parameterInfo in parameterInfos) {
if (! string .IsNullOrEmpty(parameters.ToString())) {
parameters.Append( ", " );
}
parameters.AppendFormat( "{0} {1}" , parameterInfo.ParameterType.Name, parameterInfo.Name);
}
}
return parameters.ToString();
return parameters.ToString();
}
```
.
Nicht immer ist bereits während der Entwicklung bekannt, aus welchem Assembly man Informationen benötigt. Sodass die Methode Thread.GetDomain().GetAssemblies() nicht verwendet werden kann. Eine weitere Möglichkeit an ein Assembly zu kommen ist es direkt zu laden. Dafür bietet die Klasse Assembly die statische Methode Assembly.LoadFrom zum Laden von Assemblies. Statt dem Laden des Assembly über die aktuelle Domäne, wird das nächste Beispiel so modifiziert, dass die TestProject-Assembly direkt aus einem Verzeichnis heraus geladen wird.
```
static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) {
```csharp
static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) {
Console .WriteLine( string .Format( "-- {0} ver.: {1}" , assembly.GetName().Name, assembly.GetName().Version));
Type [] types = assembly.GetTypes();
foreach ( var type in types) {
Console .WriteLine( " ++ {0}" , type.Name);
foreach ( var type in types) {
Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos =
type.GetMethods( BindingFlags .Public | BindingFlags .NonPublic | BindingFlags .Instance |
type.GetMethods( BindingFlags .Public | BindingFlags .NonPublic | BindingFlags .Instance |
BindingFlags .Static);
foreach ( var info in methodInfos) {
string modifier = GetModifier(info);
string keyword = GetKeyword(info);
string parameters = GetParameters(info);
if (! string .IsNullOrEmpty(keyword)) {
keyword = string .Format( "{0} " , keyword);
foreach ( var info in methodInfos) {
string modifier = GetModifier(info);
string keyword = GetKeyword(info);
string parameters = GetParameters(info);
if (! string .IsNullOrEmpty(keyword)) {
keyword = string .Format( "{0} " , keyword);
}
Console .WriteLine( " ++ {0} {1}{2}{3}({4})" , modifier, keyword, info.ReturnParameter,
Console .WriteLine( " ++ {0} {1}{2}{3}({4})" , modifier, keyword, info.ReturnParameter,
info.Name, parameters);
}
}
@@ -237,21 +231,21 @@ Man sieht, dass die gleiche Ausgabe wie bereits im vorherigen Beispiel generiert
Nachdem die Vorgehensweise nun klar sein sollte, möchte ich nun den eigentlich interessanten Teil demonstrieren. Wie kann man nun auf Methoden usw. zugreifen und wie können Werte aus einem Type gelesen und veränder werden? Wenn man die Methode SecondTestClass.Addition(int a, int b) aufrufen möchte, dann braucht man die Methodeninformationen von dem Type SecondTestClass aus dem TestProject-Assembly. D.h. man muss wie in den obigen Beispielen auch über alle Typen iterieren, um an den gesuchten Type SecondTestClass zu kommen. Hat man den gesuchten Type gefunden, so kann man sich von diesem alle Methoden in einem Array zurückgeben lassen. Dieses Array kann dann wieder ganz normal durchlaufen und nach der gewünschten Methode durchsucht werden. Hat man die Methode gefunden so muss zuerst eine Instanz von dem Objekt erzeugt werden und dafür gibt es den Activator, der die statische Methode CreateInstance beinhaltet. Als Parameter übergibt man ihr den Type von dem eine Instanz erzeugt werden soll. Jetzt kann man über die MethodInfo die Methode aufrufen. Dazu muss der Methode die Instanz des Objektes übergeben werden, von welchem die Methode aufgerufen werden soll und zusätzlich noch ein Array von Objekten, welche die zu übergebenen Parameter beinhaltet. Hat die Methode keine Parameter, so muss null übergeben werden. Dabei muss der Rückgabewert in den erwarteten Wert gecastet werden. Das ganze könnte dann folgendermaßen aussehen:
```
static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) {
```csharp
static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) {
Console .WriteLine( string .Format( "-- {0} ver.: {1}" , assembly.GetName().Name, assembly.GetName().Version));
Type [] types = assembly.GetTypes();
foreach ( var type in types) {
if (type.Name.Equals( "SecondTestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
foreach ( var type in types) {
if (type.Name.Equals( "SecondTestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods();
foreach ( var info in methodInfos) {
if (info.Name.Equals( "Addition" )) {
object obj = Activator .CreateInstance(type);
int value = ( int )info.Invoke(obj, new object [] { 2, 5 });
Console .WriteLine( " ++ {0}(2 + 5) = {1}" , info.Name, value);
foreach ( var info in methodInfos) {
if (info.Name.Equals( "Addition" )) {
object obj = Activator .CreateInstance(type);
int value = ( int )info.Invoke(obj, new object [] { 2, 5 });
Console .WriteLine( " ++ {0}(2 + 5) = {1}" , info.Name, value);
}
}
}
@@ -263,18 +257,17 @@ Console .ReadLine();
Aufruf der Methode Addition
.
Bei statischen Methoden kann das Erzeugen der Instanz weggelassen werden und für den Parameter des Instanz-Objektes kann null übergeben werden.
```
if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods( BindingFlags .NonPublic | BindingFlags .Static);
foreach ( var info in methodInfos) {
if (info.Name.Equals( "CalcPassword" )) {
string value = ( string )info.Invoke( null , new object [] { "myPassword" });
Console .WriteLine( " ++ {0}(\"myPassword\") = {1}" , info.Name, value);
```csharp
if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods( BindingFlags .NonPublic | BindingFlags .Static);
foreach ( var info in methodInfos) {
if (info.Name.Equals( "CalcPassword" )) {
string value = ( string )info.Invoke( null , new object [] { "myPassword" });
Console .WriteLine( " ++ {0}(\"myPassword\") = {1}" , info.Name, value);
}
}
}
@@ -282,24 +275,23 @@ Console .WriteLine( " ++ {0}(\"myPassword\") = {1}" , info.Name, value);
Das übergebene Passwort wurde über eine statische Methode gehashed.
.
Um den Wert einer Property auszulesen, muss anstelle von MethodInfo die PropertyInfo verwendet werden. Aus dieser kann dann über die Methode PropertyInfo.GetValue der aktuelle Wert zurückgegeben werden. Da in diesem Beispiel nach dem Instanzieren des Objektes noch keine Werte zugewiesen wurden, sind die Properties leer.
```
static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"C:\ TestProject.dll" );
if (assembly != null ) {
```csharp
static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"C:\ TestProject.dll" );
if (assembly != null ) {
Console .WriteLine( string .Format( "-- {0} ver.: {1}" , assembly.GetName().Name, assembly.GetName().Version));
Type [] types = assembly.GetTypes();
foreach ( var type in types) {
if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
object obj = Activator .CreateInstance(type);
PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic
| BindingFlags .Instance);
foreach ( var info in propertyInfos) {
Console .WriteLine( " ++ {0} = {1}" , info.Name, info.GetValue(obj, null ));
foreach ( var type in types) {
if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
object obj = Activator .CreateInstance(type);
PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic
| BindingFlags .Instance);
foreach ( var info in propertyInfos) {
Console .WriteLine( " ++ {0} = {1}" , info.Name, info.GetValue(obj, null ));
}
}
}
@@ -308,25 +300,23 @@ Console .ReadLine();
}
```
.
Wenn man nun die Properties mit Werten belegen möchte, so muss man analog zur Methode PropertyInfo.GetValue, die Methode PropertyInfo. SetValue verwenden.
```
if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic
| BindingFlags .Instance);
propertyInfos[0].SetValue(obj, "Peter" , null );
propertyInfos[1].SetValue(obj, "Müller" , null );
propertyInfos[2].SetValue(obj, "myPassword" , null );
foreach ( var info in propertyInfos) {
Console .WriteLine( " ++ {0} = {1}" , info.Name, info.GetValue(obj, null ));
```csharp
if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name);
PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic
| BindingFlags .Instance);
propertyInfos[0].SetValue(obj, "Peter" , null );
propertyInfos[1].SetValue(obj, "Müller" , null );
propertyInfos[2].SetValue(obj, "myPassword" , null );
foreach ( var info in propertyInfos) {
Console .WriteLine( " ++ {0} = {1}" , info.Name, info.GetValue(obj, null ));
}
}
```
.
## Wozu braucht man jetzt aber nun die Reflection?
+2 -2
View File
@@ -9,7 +9,7 @@ categories: ["Architektur", "Clean Code Developing"]
tags: ["Architektur"]
---
Wir haben wie viele von uns in der Webentwicklung vor vielen Jahren mit Scriptsprachen und mit prozeduralen Spaghetti-Code <img src="/images/softwarezellen/icon_wink.gif" alt="" class="wp-smiley"/> angefangen. Mit der Zeit wuchs unsere Webpräsenz zu einer unüberschaubaren Anwendung mit manchen Seiten, die keiner von uns mehr anfassen wollte aus Angst vor den Konsequenzen.
Wir haben wie viele von uns in der Webentwicklung vor vielen Jahren mit Scriptsprachen und mit prozeduralen Spaghetti-Code <img src="/images/softwarezellen/icon_wink.gif" alt=";)" class="wp-smiley"/> angefangen. Mit der Zeit wuchs unsere Webpräsenz zu einer unüberschaubaren Anwendung mit manchen Seiten, die keiner von uns mehr anfassen wollte aus Angst vor den Konsequenzen.
Um etwas Ordnung in die Webanwendungen zu bringen, haben wir also vor 4 Jahren angefangen, nach einem 3-Schichten-Modell zu entwickeln. Wir haben neue Funktionalitäten und neue Anwendungen nur noch so gebaut und wir waren für eine kurze Zeit zufrieden. Alles lief gut. Als wir den Umstieg auf .NET begonnen haben, haben wir weiterhin nach einem Mehr-Schichten-Modell gearbeitet, wir haben nur die Anzahl der Schichten erhöht.
@@ -27,7 +27,7 @@ Seit dem Besuch von Ralf und Stefan haben wir uns die Artikelserie von Ralf von
Wir haben noch einen langen und interessanten Weg vor uns, aber eins ist jetzt schon sicher: wir werden versuchen unsere nächste Projekte durch Softzellen modellieren.
Solange die Komplexität nicht wieder die Überhand gewinnt <img src="/images/softwarezellen/icon_wink.gif" alt="" class="wp-smiley"/>
Solange die Komplexität nicht wieder die Überhand gewinnt <img src="/images/softwarezellen/icon_wink.gif" alt=";)" class="wp-smiley"/>
## Kommentare (Archiv)
+66 -67
View File
@@ -28,47 +28,46 @@ Als Vorteile gegenüber z.B. MogileFS, CouchDB zählt für uns vor allem, dass k
Die Nutzung selbst ist denkbar einfach. Folgendes Mitarbeiter-Objekt möchte ich speichern.
```
public class Mitarbeiter
```csharp
public class Mitarbeiter
{
public int Id { get ; set ; }
public string Name { get ; set ; }
public string Vorname { get ; set ; }
public string Ort { get ; set ; }
public string Plz { get ; set ; }
72
public string VollerName
public int Id { get ; set ; }
public string Name { get ; set ; }
public string Vorname { get ; set ; }
public string Ort { get ; set ; }
public string Plz { get ; set ; }
public string VollerName
{
get { return string .Format( “{0} {1}” , Vorname, Name); }
get { return string .Format( {0} {1} , Vorname, Name); }
}
}
```
Dazu nutzt man die Put-Methode der StupidDB.
```
IStupidDB stupidDB = new StupidDB ( “test” );
Mitarbeiter mitarbeiter = new Mitarbeiter
```csharp
IStupidDB stupidDB = new StupidDB ( test );
Mitarbeiter mitarbeiter = new Mitarbeiter
{
Id = 1,
Name = “Christian” ,
Vorname = “Thomas” ,
Ort = “Rosenheim” ,
Plz = “83026”
Name = Christian ,
Vorname = Thomas ,
Ort = Rosenheim ,
Plz = 83026
};
stupidDB.Put( “TestPartition” , “EinMitarbeiter” , mitarbeiter );
stupidDB.Put( TestPartition , EinMitarbeiter , mitarbeiter );
```
Um das gleiche Objekt wieder zu lesen, nutzt man mal eine Get Methode. Die Parameter sind Partition und Key:
```
Mitarbeiter mitarbeiterAusgelesen =
```csharp
Mitarbeiter mitarbeiterAusgelesen =
( Mitarbeiter )stupidDB.GetObject(
TestPartition ,
EinMitarbeiter ,
typeof ( Mitarbeiter )
);
if (mitarbeiterAusgelesen != null )
if (mitarbeiterAusgelesen != null )
{
Console .WriteLine( Name des Mitarbeiters: {0} ,
mitarbeiterAusgelesen.VollerName);
@@ -77,13 +76,13 @@ mitarbeiterAusgelesen.VollerName);
Die StupidDB selbst wird über die app.config definiert.
```
```csharp
< configuration >
< configSections >
< section name = “ stupidDB “ type = “ Aztec.StupidDB.DBConfigCollection, Aztec.StupidDB, Version=1.0.0.0, Culture=neutral “ allowLocation = “ true “ allowDefinition = “ Everywhere “ />
< section name = stupidDB type = Aztec.StupidDB.DBConfigCollection, Aztec.StupidDB, Version=1.0.0.0, Culture=neutral allowLocation = true allowDefinition = Everywhere />
</ configSections >
< stupidDB >
< DB name = “ test “ root = “ temp\test “ tiefe = “ 3 “ versionierung = “ false “ />
< DB name = test root = temp\test tiefe = 3 versionierung = false />
</ stupidDB >
</ configuration >
```
@@ -94,77 +93,77 @@ Wer sich den aktuellen Code einmal ansehen möchte, kann dies gerne unter folgen
1. **Doku**
[March 1, 2010 at 9:59 am](/posts/stupiddb1/)
[March 1, 2010 at 9:59 am](/posts/stupiddb1/)
Coole Sache, gibt es dazu eine Doku der Funktionen wie .Backup
und der möglichen werte, wie Tiefe, Versionierung etc?
Coole Sache, gibt es dazu eine Doku der Funktionen wie .Backup
und der möglichen werte, wie Tiefe, Versionierung etc?
Vielen Dank.
Vielen Dank.
2. **[Stefan Kölle](http://www.stefankoelle.de/)**
[March 1, 2010 at 12:21 pm](/posts/stupiddb1/)
[March 1, 2010 at 12:21 pm](/posts/stupiddb1/)
Es gibt aktuell noch keine Doku, im Code wird einiges erklaert jedoch sicher nicht ausreichend.
Es gibt aktuell noch keine Doku, im Code wird einiges erklaert jedoch sicher nicht ausreichend.
In der aktuellen Ausgabe der dotnetpro gibt es jedoch einen Artikel zu StupidDB, dort erklaere ich die Parameter noch einmal genauer. Auch das Thema Tiefe, Versionierung usw. <http://www.dotnetpro.de/CurrentIssue.aspx>
In der aktuellen Ausgabe der dotnetpro gibt es jedoch einen Artikel zu StupidDB, dort erklaere ich die Parameter noch einmal genauer. Auch das Thema Tiefe, Versionierung usw. <http://www.dotnetpro.de/CurrentIssue.aspx>
3. **UweD**
[March 9, 2010 at 5:47 pm](/posts/stupiddb1/)
[March 9, 2010 at 5:47 pm](/posts/stupiddb1/)
Scheint ja wirklich toll zu sein. Leider findet man im Web noch nicht allzuviel darüber. Der Artikel in der dotnetpro sagt einiges, aber zu dem Pfad kann ich nirgends etwas finden.
Wenn ich root=“temp\test“ angebe, wo liegt das dann physisch?
Scheint ja wirklich toll zu sein. Leider findet man im Web noch nicht allzuviel darüber. Der Artikel in der dotnetpro sagt einiges, aber zu dem Pfad kann ich nirgends etwas finden.
Wenn ich root=“temp\test“ angebe, wo liegt das dann physisch?
4. **[Stefan Kölle](http://www.stefankoelle.de/)**
[March 9, 2010 at 5:52 pm](/posts/stupiddb1/)
[March 9, 2010 at 5:52 pm](/posts/stupiddb1/)
Hallo, Uwe,
Hallo, Uwe,
man kann bei root einfach einen absoluten Pfad angeben, z.B. c:\temp\stupiddb\
man kann bei root einfach einen absoluten Pfad angeben, z.B. c:\temp\stupiddb\
Ohne der Angabe eines Pfades wird die StupidDB im Ausfuehrungsverzeichnis angelegt.
Ohne der Angabe eines Pfades wird die StupidDB im Ausfuehrungsverzeichnis angelegt.
Gruss
Stefan
Gruss
Stefan
5. **Uwe**
[January 4, 2011 at 11:17 am](/posts/stupiddb1/)
[January 4, 2011 at 11:17 am](/posts/stupiddb1/)
Hallo,
ich bin nun nicht so firm in C#. Gibt es Beispiel für IStupidDB.Dir ?
Hallo,
ich bin nun nicht so firm in C#. Gibt es Beispiel für IStupidDB.Dir ?
Danke
Uwe
Danke
Uwe
6. **[Andreas Seebauer](http://www.in-your-face.org/)**
[January 12, 2011 at 12:05 pm](/posts/stupiddb1/)
[January 12, 2011 at 12:05 pm](/posts/stupiddb1/)
Hallo, Uwe,
Hallo, Uwe,
hier ein kleines Beispiel für die Verwendung von IStupidDB.Dir in C#:
hier ein kleines Beispiel für die Verwendung von IStupidDB.Dir in C#:
IStupidDB sdb = new StupidDB(“meinDbName”);
string partition = “Beispielpartition”;
string key = “Beispielkey”;
// Speichern des strings “Beispiel” unter Key “Beispielkey” in Partition “Beispielpartition”
sdb.Put(partition, key, “Beispiel”);
// Auflisten aller Keys in Partition “Beispielpartition”
List listKeys = sdb.Dir(partition);
IStupidDB sdb = new StupidDB(“meinDbName”);
string partition = “Beispielpartition”;
string key = “Beispielkey”;
// Speichern des strings “Beispiel” unter Key “Beispielkey” in Partition “Beispielpartition”
sdb.Put(partition, key, “Beispiel”);
// Auflisten aller Keys in Partition “Beispielpartition”
List listKeys = sdb.Dir(partition);
Mit den zurückgelieferten Keys können dann die Objekte via GetObject ausgelesen werden.
Ich hoffe das beantwortet deine Frage.
Mit den zurückgelieferten Keys können dann die Objekte via GetObject ausgelesen werden.
Ich hoffe das beantwortet deine Frage.
Gruß
Andreas
Gruß
Andreas
7. **Uwe**
[January 12, 2011 at 4:23 pm](/posts/stupiddb1/)
[January 12, 2011 at 4:23 pm](/posts/stupiddb1/)
Hallo Andreas,
nein, nicht ganz!
Laut Doc gibt es
Dir Listet alle Schlüssel einer Partition) und
Dir Liefert ein IDictionary aller Schlüssel mit zugehörigem Objekt .
Hallo Andreas,
nein, nicht ganz!
Laut Doc gibt es
Dir Listet alle Schlüssel einer Partition) und
Dir Liefert ein IDictionary aller Schlüssel mit zugehörigem Objekt .
Mit geht es um das 2. Dir.
Mit geht es um das 2. Dir.
Uwe
Uwe
+75 -92
View File
@@ -15,72 +15,64 @@ Nehmen wir zum Beispiel ein einfaches Formular. Nach Absenden des Formulars soll
<img src="/images/unittests-webforms/TestableWebForm.jpg" alt="Testable WebForm"/>   <img src="/images/unittests-webforms/TestableWebForm_Sent.jpg" alt="Testable WebForm Sent"/>
Und nun zum Quellcode: Die automatisch erstellte .designer.cs muss entfernt werden, was man sowieso tun sollte, da man automatisch erstellten Code also Code, den keiner außer Microsoft unter Kontrolle hat vermeiden sollte.
Und nun zum Quellcode: Die automatisch erstellte .designer.cs muss entfernt werden, was man sowieso tun sollte, da man automatisch erstellten Code also Code, den keiner außer Microsoft unter Kontrolle hat vermeiden sollte.
Die Inhalte der .designer.cs also die Definitionen der Web-Elemente werden in der Klasse als `Public Properties` erstellt und instantiiert, um bei Zugriffen wie `TextBox.Text` keine NullReferenceException zu bekommen.
```
using System;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using framework.Testable.Web.UI;
5
namespace TestableWebForm
```csharp
using System;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using framework.Testable.Web.UI;
namespace TestableWebForm
{
public class DefaultPage : System.Web.UI. Page
public class DefaultPage : System.Web.UI. Page
{
10
#region Controls
public HtmlForm Formular;
public TextBox Value1 = new TextBox ();
public TextBox Value2 = new TextBox ();
public Label Result = new Label ();
public Button Submit = new Button ();
#region Controls
public HtmlForm Formular;
public TextBox Value1 = new TextBox ();
public TextBox Value2 = new TextBox ();
public Label Result = new Label ();
public Button Submit = new Button ();
#endregion
```
Um das Verhalten testen zu können, haben wir Adapter für die Klassen `System.Web.UI.Page`, `System.Web.HttpRequest` und `System.Web.HttpResponse` geschrieben, und zwar für die Properties und Methoden die uns vorerst interessieren: z.B. `Page.IsPostBack, Page.Request, Response.Redirect(string url, bool endResponse)`. Bei der Benennung haben wir einfach den Namespace `System` mit `framework.Testable` ersetzt und wir haben natürlich zu jedem Testable-Objekt einen Interface erstellt.
```
1
namespace framework.Testable.Web
```csharp
namespace framework.Testable.Web
{
namespace UI
namespace UI
{
public interface IPage
public interface IPage
{
bool IsPostBack{ get ; }
IHttpRequest Request{ get ; set ; }
IHttpResponse Response{ get ; set ; }
bool IsPostBack{ get ; }
IHttpRequest Request{ get ; set ; }
IHttpResponse Response{ get ; set ; }
}
12
public class Page : IPage
public class Page : IPage
{
private readonly System.Web.UI. Page m_page;
private IHttpRequest m_request;
private IHttpResponse m_response;
18
public Page( System.Web.UI. Page page )
private readonly System.Web.UI. Page m_page;
private IHttpRequest m_request;
private IHttpResponse m_response;
public Page( System.Web.UI. Page page )
{
m_page = page;
m_request = new HttpRequest ( m_page );
m_response = new HttpResponse ( m_page );
m_request = new HttpRequest ( m_page );
m_response = new HttpResponse ( m_page );
}
25
public bool IsPostBack
public bool IsPostBack
{
get { return m_page.IsPostBack; }
get { return m_page.IsPostBack; }
}
30
public IHttpRequest Request
public IHttpRequest Request
{
get { return m_request; }
set { m_request = value ; }
get { return m_request; }
set { m_request = value ; }
}
36
public IHttpResponse Response
public IHttpResponse Response
{
get { return m_response; }
set { m_response = value ; }
get { return m_response; }
set { m_response = value ; }
}
}
}
@@ -91,68 +83,61 @@ Um alle gemockte Objekte setzen zu können, haben wir unserer `Page`-Klasse auch
Das war ungefähr alles: in der Seite nutzt man dann anstelle der eigenen Request und Response-Objekten die `Testable`-Objekte.
```
public class DefaultPage : System.Web.UI. Page
```csharp
public class DefaultPage : System.Web.UI. Page
{
private IPage m_page;
public void SetTestableObjects( IPage page )
private IPage m_page;
public void SetTestableObjects( IPage page )
{
m_page = page;
}
24
public void Page_Load( object sender, EventArgs e )
public void Page_Load( object sender, EventArgs e )
{
if (m_page == null ) m_page = new Page ( this );
int multiple = 1;
if (! string .IsNullOrEmpty( m_page.Request.GetParamValue( "multiple" ) )) multiple = Convert .ToInt32( m_page.Request.GetParamValue( "multiple" ) );
if (m_page.IsPostBack)
if (m_page == null ) m_page = new Page ( this );
int multiple = 1;
if (! string .IsNullOrEmpty( m_page.Request.GetParamValue( "multiple" ) )) multiple = Convert .ToInt32( m_page.Request.GetParamValue( "multiple" ) );
if (m_page.IsPostBack)
{
Result.Text = ( ( Convert .ToInt32( Value1.Text ) + Convert .ToInt32( Value2.Text ))*multiple ).ToString();
Result.Text = ( ( Convert .ToInt32( Value1.Text ) + Convert .ToInt32( Value2.Text ))*multiple ).ToString();
}
}
```
Damit ist die Web-Anwendung bereit zum Testen. So schaut zum Beispiel ein Test für das Laden der Seite aus:
```
using framework.Testable.Web;
using framework.Testable.Web.UI;
using NUnit.Framework;
using Rhino.Mocks;
using TestableWebForm;
6
namespace Tests
```csharp
using framework.Testable.Web;
using framework.Testable.Web.UI;
using NUnit.Framework;
using Rhino.Mocks;
using TestableWebForm;
namespace Tests
{
[ TestFixture ]
public class WebFormTests
public class WebFormTests
{
private IPage m_page;
private IHttpRequest m_request;
private IHttpResponse m_response;
private DefaultPage m_defaultPage;
16
private IPage m_page;
private IHttpRequest m_request;
private IHttpResponse m_response;
private DefaultPage m_defaultPage;
[ SetUp ]
public void Init()
public void Init()
{
m_page = MockRepository .GenerateStub< IPage >();
m_request = MockRepository .GenerateStub< IHttpRequest >();
m_response = MockRepository .GenerateStub< IHttpResponse >();
m_page = MockRepository .GenerateStub< IPage >();
m_request = MockRepository .GenerateStub< IHttpRequest >();
m_response = MockRepository .GenerateStub< IHttpResponse >();
m_page.Request = m_request;
m_page.Response = m_response;
}
26
[ Test ]
public void PageLoad_Loading_EmptyFields()
public void PageLoad_Loading_EmptyFields()
{
// Arrange
31
32
// Act
m_defaultPage = new DefaultPage ();
m_defaultPage = new DefaultPage ();
m_defaultPage.SetTestableObjects(m_page);
m_defaultPage.Page_Load( null , null );
37
m_defaultPage.Page_Load( null , null );
// Assert
Assert .IsEmpty( m_defaultPage.Value1.Text );
Assert .IsEmpty( m_defaultPage.Value2.Text );
@@ -162,24 +147,22 @@ Assert .IsEmpty(m_defaultPage.Result.Text);
Und so für PostBack inklusive QueryString-Parameter:
```
```csharp
[ Test ]
public void PageLoad_PostBackWithRequestValue_ResultIsCorrect()
public void PageLoad_PostBackWithRequestValue_ResultIsCorrect()
{
// Arrange
const int value1 = 1;
const int value2 = 2;
const int value3 = 3;
m_request.Expect( a => a.GetParamValue( "multiple" ) ).IgnoreArguments().Repeat.Twice().Return( value3.ToString() );
m_page.Expect( a => a.IsPostBack ).Return( true );
72
const int value1 = 1;
const int value2 = 2;
const int value3 = 3;
m_request.Expect( a => a.GetParamValue( "multiple" ) ).IgnoreArguments().Repeat.Twice().Return( value3.ToString() );
m_page.Expect( a => a.IsPostBack ).Return( true );
// Act
m_defaultPage = new DefaultPage ();
m_defaultPage = new DefaultPage ();
m_defaultPage.SetTestableObjects( m_page );
m_defaultPage.Value1.Text = value1.ToString();
m_defaultPage.Value2.Text = value2.ToString();
m_defaultPage.Page_Load( null , null );
79
m_defaultPage.Page_Load( null , null );
// Assert
Assert .IsTrue( m_defaultPage.Result.Text == ( (value1 + value2)*value3 ).ToString() );
}
@@ -187,6 +170,6 @@ Assert .IsTrue( m_defaultPage.Result.Text == ( (value1 + value2)*value3 ).ToStri
Dieses Vorgehen hat uns nicht nur den seit langen gesuchten Weg zum Testen von Webanwendungen geebnet, sondern zwingt auch den Entwickler dazu, alle Funktionalitäten, die nicht in einer Webseite sondern in die dll-s gehören, auszulagern. Damit dürfte es auch der richtige Weg der Clean Code Developers für die Arbeit mit WebForms sein.
Was die Adapter-Klassen betrifft: inzwischen haben wir auch System.IO “adaptiert” und bald werden die anderen System-Klassen folgen, je nach Bedarf.
Was die Adapter-Klassen betrifft: inzwischen haben wir auch System.IO “adaptiert” und bald werden die anderen System-Klassen folgen, je nach Bedarf.
[Download VS2008-Projekt](/assets/TestableWebForm.zip)
+1 -1
View File
@@ -50,7 +50,7 @@ Glückwunsch nochmal an die Gewinner des Wettbewerbs (Schiffe versenken).
Hallo Ralph,
natürlich nicht <img src="/images/webcamps/icon_smile.gif" alt="" class="wp-smiley"/> . Für die Veranstalter war es nur wichtig, dass man sich mit den Technologien beschäftigt. Ob der Code clean war oder nicht, war völlig egal.
natürlich nicht <img src="/images/webcamps/icon_smile.gif" alt=":-)" class="wp-smiley"/> . Für die Veranstalter war es nur wichtig, dass man sich mit den Technologien beschäftigt. Ob der Code clean war oder nicht, war völlig egal.
Wir wollten es eben so machen wie an der Arbeit.
Gruß
+14 -14
View File
@@ -9,7 +9,7 @@ categories: ["Webservice"]
tags: [".NET", "ASP-Classic", "Refaktorisierung", "Webservice"]
---
Seit einiger Zeit sind wir dabei, unser Portal zu refaktorisieren. Das entfernte Ziel ist, alles in .NET um zu bauen. Das betrifft zur Zeit über tausend Seiten aber wir möchten noch in diesem Leben fertig sein <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt="" class="wp-smiley"/>
Seit einiger Zeit sind wir dabei, unser Portal zu refaktorisieren. Das entfernte Ziel ist, alles in .NET um zu bauen. Das betrifft zur Zeit über tausend Seiten aber wir möchten noch in diesem Leben fertig sein <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt=";)" class="wp-smiley"/>
Dazu kommt noch, dass wir selbstverständlich agiles Softwarentwicklung betreiben, was eine ständige und rhythmische Iteration vorsieht, keine Mega-Projekte mit ungewissem Ende. Also haben wir die verschiedenen Bereiche identifiziert und diese werden einer nach dem anderen neu gebaut.
Die neuen DLL-s in .NET werden mit Hilfe von verschiedenen Webservices angesprochen. Das führt allerdings unweigerlich dazu, 2 Technologien ASP-Classic und .NET(C#) miteinander sicher kommunizieren zu lassen. Das funktioniert so:
@@ -110,15 +110,15 @@ Noch ein paar Bemerkungen:
[Dezember 15, 2009 um 10:49 am](/posts/webservice-mit-asp-classic/)
Klar, das funktioniert mit allen SOAP-Webservices. Die Fehlermeldung ist typisch Microsoft, kann alles mögliche bedeuten <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt="" class="wp-smiley"/>
Klar, das funktioniert mit allen SOAP-Webservices. Die Fehlermeldung ist typisch Microsoft, kann alles mögliche bedeuten <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt=";)" class="wp-smiley"/>
Was steht bei dir in Zeile 34?
3. **kai**
[Dezember 15, 2009 um 11:07 am](/posts/webservice-mit-asp-classic/)
Oh… mir fällt gerade auf (ich nahm diesen Webservice, weil er LÄUFT und im Netz funktionsfähig ist), dass dieser Webservice eine Guid über einen Login generieren muss, deswegen funktioniert es mit Sicherheit nicht! <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt="" class="wp-smiley"/>
Oh… mir fällt gerade auf (ich nahm diesen Webservice, weil er LÄUFT und im Netz funktionsfähig ist), dass dieser Webservice eine Guid über einen Login generieren muss, deswegen funktioniert es mit Sicherheit nicht! <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt=";)" class="wp-smiley"/>
Werde erstmal weiter schauen, trotzdem vielen Dank für die schnelle Antwort! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt="" class="wp-smiley"/>
Werde erstmal weiter schauen, trotzdem vielen Dank für die schnelle Antwort! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Falls es nichts wird, melde ich mich noch einmal!
4. **kai**
@@ -127,8 +127,8 @@ Noch ein paar Bemerkungen:
strRet = objRequest.responseText steht in dieser Zeile.
Mit <http://localhost:port> und einer C#-Anwendung (lokal) kann ich auf den Dienst zugreifen, von meinem IIS-Server mit entsprechend geänderter
SOAP-URL (<http://IP:port>) und ausgeschalteter Firewall kommt es zu der Fehlermeldung.
Mit `http://localhost:port` und einer C#-Anwendung (lokal) kann ich auf den Dienst zugreifen, von meinem IIS-Server mit entsprechend geänderter
SOAP-URL (`http://IP:port`) und ausgeschalteter Firewall kommt es zu der Fehlermeldung.
Ich benutze die automatisch mit generierte HelloWorld-Funktion, welche ich via ASP aufrufe.
@@ -147,12 +147,12 @@ Noch ein paar Bemerkungen:
[Dezember 15, 2009 um 1:00 pm](/posts/webservice-mit-asp-classic/)
Ich will nicht spammen, wollte dir das aber noch mit auf den Weg geben: Vielen Dank für den Code! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt="" class="wp-smiley"/>
Ich will nicht spammen, wollte dir das aber noch mit auf den Weg geben: Vielen Dank für den Code! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt=":-)" class="wp-smiley"/>
8. **Christina Hirth**
[Dezember 15, 2009 um 1:03 pm](/posts/webservice-mit-asp-classic/)
Hei Kai, kein Problem <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt="" class="wp-smiley"/>
Hei Kai, kein Problem <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Du musst diesen Webservice Publishen, ihn auf einen erreichbaren Webserver veröffentlichen. Dann müsste alles passen.
9. **kai**
@@ -163,9 +163,9 @@ Noch ein paar Bemerkungen:
Und jetzt habe ich das erst mit den strParameters verstanden, ich dachte die bräuchte man nur, wenn man der Methode des Webservice via SOAP Parameter übergeben wollen würde, dies war bis gerade auskommentiert.
Es waren also 2 Sachen zu bewältigen… <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt="" class="wp-smiley"/>
Es waren also 2 Sachen zu bewältigen… <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt=";)" class="wp-smiley"/>
Jetzt läuft es, vielen Dank! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt="" class="wp-smiley"/>
Jetzt läuft es, vielen Dank! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Eine Frage bleibt bei mir aber noch offen!
@@ -173,12 +173,12 @@ Noch ein paar Bemerkungen:
Ist der Parameter überhaupt notwendig? Eigentlich teilt man doch schon über den Soap-Envelope (strParameters) mit, welche Funktion des Webservice man aufrufen möchte oder nicht?
Wirklich, vielen Dank, Christina! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt="" class="wp-smiley"/>
Wirklich, vielen Dank, Christina! <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt=":-)" class="wp-smiley"/>
10. **Christina Hirth**
[Dezember 15, 2009 um 3:20 pm](/posts/webservice-mit-asp-classic/)
Ich weiß nicht genau, aber ich könnte mir sehr gut vorstellen, dass weil im Webservice eine einzige Funktion vorhanden ist, ist diese sozusagen die “Default”-Adresse <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt="" class="wp-smiley"/>
Ich weiß nicht genau, aber ich könnte mir sehr gut vorstellen, dass weil im Webservice eine einzige Funktion vorhanden ist, ist diese sozusagen die “Default”-Adresse <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt=";)" class="wp-smiley"/>
Aber die Frage ist interessant, ich werde das bei der nächsten Gelegenheit überprüfen.
Es freut mich sehr, dass du auch so begeistert bist, wie ich damals war, als es das erste mal diese alte Technik-neue Technik Kommunikation hingehauen hat
@@ -196,9 +196,9 @@ Noch ein paar Bemerkungen:
Deine Default-Theorie kann es leider nicht sein, weil ich bereits 2 Methoden in meinem Webservice habe.
Sehr interessant… <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt="" class="wp-smiley"/>
Sehr interessant… <img src="/images/webservice-mit-asp-classic/icon_smile.gif" alt=":-)" class="wp-smiley"/>
Nunja, hauptsache es funktioniert, WIE ist nur halb so wichtig! <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt="" class="wp-smiley"/>
Nunja, hauptsache es funktioniert, WIE ist nur halb so wichtig! <img src="/images/webservice-mit-asp-classic/icon_wink.gif" alt=";)" class="wp-smiley"/>
Wünsche dir ebenfalls schöne Feiertage, bin für heute erstmal raus.
+3
View File
@@ -0,0 +1,3 @@
---
title: "Schlagwörter"
---
+10 -5
View File
@@ -11,6 +11,8 @@
{{- $desc := "" }}
{{- if .IsHome }}
{{- $desc = .Site.Params.description }}
{{- else if .Description }}
{{- $desc = .Description }}
{{- else if .Content }}
{{- $desc = .Content | plainify }}
{{- else if .IsNode }}
@@ -57,8 +59,11 @@
{{- if not $desc }}{{ $desc = .Summary | plainify }}{{ end }}
{{- $desc = strings.TrimSpace (strings.TrimRight "…" $desc) | truncate 160 }}
{{- if $desc }}<meta name="description" content="{{ $desc }}">{{ end }}
<link rel="canonical" href="{{ .Permalink }}">
<meta property="og:type" content="{{ if .IsHome }}website{{ else }}article{{ end }}">
{{- if eq .Kind "404" }}<meta name="robots" content="noindex">{{ end }}
{{- if ne .Kind "404" }}<link rel="canonical" href="{{ .Permalink }}">{{ end }}
{{- $ogType := "website" }}
{{- if .IsPage }}{{ $ogType = "article" }}{{ end }}
<meta property="og:type" content="{{ $ogType }}">
<meta property="og:site_name" content="{{ .Site.Title }}">
<meta property="og:title" content="{{ $title }}">
<meta property="og:url" content="{{ .Permalink }}">
@@ -66,7 +71,7 @@
{{- if .Params.images }}
{{- range .Params.images }}<meta property="og:image" content="{{ . | absURL }}">{{ end }}
{{- else }}<meta property="og:image" content="{{ .Site.Params.bannerImage | absURL }}">{{ end }}
{{- if not .IsHome }}{{ with .Date }}<meta property="article:published_time" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{ end }}{{ end }}
{{- if .IsPage }}{{ with .Date }}<meta property="article:published_time" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{ end }}{{ end }}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ $title }}">
{{- if $desc }}<meta name="twitter:description" content="{{ $desc }}">{{ end }}
@@ -95,7 +100,7 @@
</main>
<footer class="site-footer">
<p>&copy; 2009-{{ now.Format "2006" }} build broken &middot; <a href="{{ "/impressum/" | relURL }}">Impressum</a> &middot; archiviert als statische Seite &middot; part of the <a href="https://hub.moonweb.org">moonweb.org</a> homelab.</p>
<p>&copy; 2009-{{ now.Format "2006" }} build broken &middot; <a href="{{ "/impressum/" | relURL }}">Impressum</a> &middot; archiviert als statische Seite &middot; part of the <a href="https://www.moonweb.org">moonweb.org</a> homelab.</p>
</footer>
<dialog id="broken-link-dialog" class="broken-link-dialog">
@@ -109,7 +114,7 @@
</div>
</dialog>
<script type="application/json" id="link-status-data">{{ (dict "broken" (site.Data.links.broken | default dict) "replacement" (site.Data.links.replacement | default dict)) | jsonify | safeJS }}</script>
<script type="application/json" id="link-status-data">{{ (dict "broken" (hugo.Data.links.broken | default dict) "replacement" (hugo.Data.links.replacement | default dict)) | jsonify | safeJS }}</script>
<script src="{{ "js/link-status.js" | relURL }}"></script>
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
{{- $pages := where .Site.RegularPages "Section" "!=" "" }}
{{- $pages = where $pages "File.ContentBase" "!=" "impressum" }}
{{- $limit := .Site.Config.Services.RSS.Limit }}
{{- if ge $limit 1 }}
{{- $pages = $pages | first $limit }}
{{- end }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ .Site.Title }}</title>
<link>{{ .Site.BaseURL }}</link>
<description>{{ with .Site.Params.description }}{{ . }}{{ end }}</description>
<generator>Hugo -- gohugo.io</generator>
{{- with .Language.Locale }}<language>{{ . }}</language>{{ end }}
{{- range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</pubDate>
<guid>{{ .Permalink }}</guid>
<description>{{ .Summary | html }}</description>
{{- with .Params.author }}<author>{{ . }}</author>{{ end }}
</item>
{{- end }}
</channel>
</rss>
+1 -1
View File
@@ -1,7 +1,7 @@
{{ define "main" }}
{{ $paginator := .Paginate (where .Site.RegularPages "Type" "posts") }}
{{ if eq $paginator.PageNumber 1 }}
<h1 class="welcome-title">Willkommen auf unserem dotnet developer blog 👋</h1>
<h2 class="welcome-title">Willkommen auf unserem dotnet developer blog 👋</h2>
{{ with .Content }}
<div class="about-intro">{{ . }}</div>
{{ end }}