fix code blocks

This commit is contained in:
2026-09-10 22:54:41 +02:00
parent 2dc30200a1
commit cd8a75ec13
9 changed files with 519 additions and 666 deletions
+3 -3
View File
@@ -13,7 +13,7 @@ Anonyme Datentypen sind Klassen, die erst beim Kompilieren durch den Compiler de
Definiert und intstanziert werden sie mit new und einem Objektinitialisierer: Definiert und intstanziert werden sie mit new und einem Objektinitialisierer:
``` ```csharp
var mitarbeiter = new { Name = "Müller", Abteilung = "IT" }; var mitarbeiter = new { Name = "Müller", Abteilung = "IT" };
``` ```
@@ -21,7 +21,7 @@ var ist der implizite Typ und **nicht** der anonyme Datentyp. Er fungiert hier l
Gibt man keinen Namen für die Properties an, werden die Namen der Properties verwendet, mit denen initialisiert wird. Gibt man keinen Namen für die Properties an, werden die Namen der Properties verwendet, mit denen initialisiert wird.
``` ```csharp
var abteilung = new { Abteilungsname = "IT" }; var abteilung = new { Abteilungsname = "IT" };
var mitarbeiter = new { Name = "Müller" , abteilung.Abteilungsname }; var mitarbeiter = new { Name = "Müller" , abteilung.Abteilungsname };
Console .WriteLine(mitarbeiter.Abteilungsname); Console .WriteLine(mitarbeiter.Abteilungsname);
@@ -33,7 +33,7 @@ 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 : 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 :
``` ```csharp
public void Action() { public void Action() {
IList < Mitarbeiter > mitarbeiterListe = m_service.GetMitarbeiter(); IList < Mitarbeiter > mitarbeiterListe = m_service.GetMitarbeiter();
var mitarbeiterMitHoroskopListe = ( from mitarbeiter in mitarbeiterListe var mitarbeiterMitHoroskopListe = ( from mitarbeiter in mitarbeiterListe
+63 -104
View File
@@ -8,137 +8,96 @@ author: "Thomas Christian"
categories: ["Architektur", "Pattern", "Tipps"] 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() static void Main()
{ {
CalcProxy calcProxy = new CalcProxy(new Calculator());
CalcProxy calcProxy = new CalcProxy(new Calculator()); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.EnableVisualStyles(); Application.Run(new Form1(calcProxy));
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. 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 { 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) { private void Run_Click(object sender, EventArgs e) {
int number;
        InitializeComponent(); if (Int32.TryParse(txbEingabe.Text, out number)) {
m_calcProxy.CalcAsync(number, number);
        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();
    }
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. 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 { 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; private void ExecuteCalc(object state) {
var result = Calc((int)state);
public CalcProxy(ICalculator calculator) { m_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result);
}
        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 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. 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 class Calculator : ICalculator {
public int Calc(int number) {
public int Calc(int number) { Thread.Sleep(10000);
return number * number;
Thread.Sleep(10000); }
return number \* number;
    }
} }
```
## Kommentare (Archiv) ## Kommentare (Archiv)
@@ -155,7 +114,7 @@ return number \* number;
[Mai 6, 2010 um 4:38 pm](/posts/async-refactored/) [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)** 3. **[Rainer Hilmer](http://dotnet-forum.de/blogs/rainerhilmer/default.aspx)**
[Juli 24, 2010 um 1:56 pm](/posts/async-refactored/) [Juli 24, 2010 um 1:56 pm](/posts/async-refactored/)
+56 -115
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. Bei dem Drücken des „Run“-Buttons, wird die Methode Calculator.CalcAsync(…) aufgerufen.
``` ```csharp
public partial class Form1 : Form { public partial class Form1 : Form {
private ICalculator m_calculator; private ICalculator m_calculator;
public Form1() { public Form1() {
InitializeComponent(); InitializeComponent();
m_calculator = new Calculator (); m_calculator = new Calculator();
m_calculator.CalcCompleted += Calculator_CalcCompleted; m_calculator.CalcCompleted += Calculator_CalcCompleted;
} }
private void Run_Click( object sender, EventArgs e) { private void Run_Click(object sender, EventArgs e) {
int number; int number;
if ( Int32 .TryParse(txbEingabe.Text, out number)) { if (Int32.TryParse(txbEingabe.Text, out number)) {
m_calculator.CalcAsync(number, number); m_calculator.CalcAsync(number, number);
} }
} }
void Calculator_CalcCompleted( object sender, CalcEventArgs eventArgs) { void Calculator_CalcCompleted(object sender, CalcEventArgs eventArgs) {
lblCounter.Text = eventArgs.Result.ToString(); 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: Der Calculator sieht wie folgt aus:
``` ```csharp
public interface ICalculator { public interface ICalculator {
event Calculator . CalcCompletedEventHandler CalcCompleted; event Calculator.CalcCompletedEventHandler CalcCompleted;
int Calc( int number); int Calc(int number);
void CalcAsync( int number, object userState); void CalcAsync(int number, object userState);
} }
public class Calculator : ICalculator { public class Calculator : ICalculator {
public delegate void CalcCompletedEventHandler ( object sender, CalcEventArgs eventArgs); public delegate void CalcCompletedEventHandler(object sender, CalcEventArgs eventArgs);
public event CalcCompletedEventHandler CalcCompleted; public event CalcCompletedEventHandler CalcCompleted;
private AsyncOperation m_asyncOperation; private AsyncOperation m_asyncOperation;
private bool m_isRunning; private bool m_isRunning;
public int Calc( int number) { public int Calc(int number) {
Thread .Sleep(10000); Thread.Sleep(10000);
return number * number; 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) { public class CalcEventArgs : AsyncCompletedEventArgs {
lock ( this ) { public CalcEventArgs(Exception error, bool cancelled, int result, object userState)
if (m_isRunning) { : base(error, cancelled, userState) {
throw new InvalidOperationException ( "Diese Operation wird bereits ausgeführt" ); Result = result;
} }
m_isRunning = true ; public int Result { get; private set; }
m_asyncOperation = AsyncOperationManager .CreateOperation(userState);
ThreadPool .QueueUserWorkItem(ExecuteCalc, number);
}
}
```
        .
        .
        .
```
} }
``` ```
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. 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 ; }
}
```
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.). 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) ## 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/)** 1. <img src="/images/async/3e7b6cb163ea9cc8549abcb8b5a03e2e.jpeg" width="32" height="32" class="avatar avatar-32 photo" alt="Avatar"/> **[Ralf Westphal](http://ralfw.blogspot.com/)**
+1 -1
View File
@@ -21,7 +21,7 @@ Hierfür habe ich folgende Konfiguration verwendet:
Nach der Bestätigung der Konfiguration sieht das Ergebnis folgendermaßen aus : Nach der Bestätigung der Konfiguration sieht das Ergebnis folgendermaßen aus :
``` ```csharp
/// <summary> /// <summary>
/// Summary /// Summary
/// </summary> /// </summary>
+5 -16
View File
@@ -15,7 +15,7 @@ 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. Jetzt zum Code: Nehmen wir eine ganz einfache Klasse `Bill` deren Objekte mit einem `IRepository` gespeichert bzw. geladen werden.
``` ```csharp
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
namespace ContractsPrototyp namespace ContractsPrototyp
{ {
@@ -25,8 +25,6 @@ public int Id { get ; set ; }
public string Number { get ; set ; } public string Number { get ; set ; }
public double Value { get ; set ; } public double Value { get ; set ; }
} }
10
11
public interface IRepository public interface IRepository
{ {
Bill GetBill( string number); Bill GetBill( string number);
@@ -36,7 +34,7 @@ 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: 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 ))] [ ContractClass ( typeof ( RepositoryContracts ))]
public interface IRepository public interface IRepository
{ {
@@ -51,7 +49,6 @@ public Bill GetBill( string number)
Contract .Requires(! string .IsNullOrEmpty(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); Contract .Ensures(bill.Id > 0);
@@ -63,7 +60,7 @@ Eine Vorbedingung wird mit `Contract.Requires` und eine Nachbedingung mit `Contr
Die eigentliche Implementierung der Klasse schaut dann so aus: Die eigentliche Implementierung der Klasse schaut dann so aus:
``` ```csharp
public class Repository : IRepository public class Repository : IRepository
{ {
public Bill GetBill( string nummer) public Bill GetBill( string nummer)
@@ -71,13 +68,11 @@ public Bill GetBill( string nummer)
//Würde das Objekt aus Datenhaltung laden //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 //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);
@@ -88,13 +83,12 @@ 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: Bei Kontraktverletzung wird eine Exception geworfen. Um diese und dadurch die genaue Verletzung überprüfen zu können braucht man etwas Workaround:
``` ```csharp
[ TestFixture ] [ TestFixture ]
public class BillTests public class BillTests
{ {
private IRepository m_repository; private IRepository m_repository;
private string m_message; private string m_message;
60
[ SetUp ] [ SetUp ]
public void Setup() public void Setup()
{ {
@@ -110,11 +104,10 @@ m_message = e.Message;
Danach sind die Tests dann einfach: Danach sind die Tests dann einfach:
``` ```csharp
[ Test ] [ Test ]
public void Laden_mit_leerer_Nummer_verletzt_Kontrakt() public void Laden_mit_leerer_Nummer_verletzt_Kontrakt()
{ {
76
try try
{ {
m_repository.GetBill( null ); m_repository.GetBill( null );
@@ -123,14 +116,11 @@ catch
{ {
//Nichts //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 ] [ Test ]
public void Speichern_Rechnung_ohne_Nummer_verletzt_Kontrakt() public void Speichern_Rechnung_ohne_Nummer_verletzt_Kontrakt()
{ {
92
try try
{ {
m_repository.SaveBill( new Bill {Value = 25} ); m_repository.SaveBill( new Bill {Value = 25} );
@@ -139,7 +129,6 @@ catch
{ {
//Nichts //Nichts
} }
101
Assert .That( m_message, Is .EqualTo( "Postcondition failed: bill.Id > 0" ) ); Assert .That( m_message, Is .EqualTo( "Postcondition failed: bill.Id > 0" ) );
} }
``` ```
+3 -11
View File
@@ -19,18 +19,16 @@ Voraussetzungen für dieses Pattern sind:
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. 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.
``` ```csharp
public abstract class Plugin { public abstract class Plugin {
private IHost m_host; private IHost m_host;
public Plugin( string name) { public Plugin( string name) {
Name = name; Name = name;
} }
6
public string Name { get ; set ; } public string Name { get ; set ; }
public string Author { get ; set ; } public string Author { get ; set ; }
public string Version { get ; set ; } public string Version { get ; set ; }
public bool IsRegistered { get ; private set ; } public bool IsRegistered { get ; private set ; }
11
/// <summary> /// <summary>
/// Setzt oder gibt die Host-Application. /// Setzt oder gibt die Host-Application.
/// </summary> /// </summary>
@@ -63,7 +61,7 @@ Zum einen ist es in der Regel so, dass ein Plug-In für nur einen Host entwickel
Das Interface IHost sieht folgendermaßen aus: Das Interface IHost sieht folgendermaßen aus:
``` ```csharp
public interface IHost { public interface IHost {
bool Register(Plugin plugin); bool Register(Plugin plugin);
bool Unregister(Plugin plugin); bool Unregister(Plugin plugin);
@@ -74,14 +72,12 @@ Dieses Interface muss von der Host-Applikation implementiert werden, damit sich
Eine vereinfachte Darstellung der Implementierung des IHost -Interfaces sieht folgendermaßen aus: Eine vereinfachte Darstellung der Implementierung des IHost -Interfaces sieht folgendermaßen aus:
``` ```csharp
public class HostApplication : IHost { public class HostApplication : IHost {
private List<Plugin> m_pluginList; private List<Plugin> m_pluginList;
3
//Konstruktor //Konstruktor
public HostApplication() { public HostApplication() {
m_pluginList = new List<Plugin>(); m_pluginList = new List<Plugin>();
7
//Lädt alle verfügbaren Plug-Ins zb. aus einem Verzeichnis //Lädt alle verfügbaren Plug-Ins zb. aus einem Verzeichnis
List<Plugin> plugins = GetPlugins(); List<Plugin> plugins = GetPlugins();
foreach (Plugin plugin in plugins) { foreach (Plugin plugin in plugins) {
@@ -89,21 +85,18 @@ foreach (Plugin plugin in plugins) {
plugin.Host = this ; plugin.Host = this ;
} }
} }
15
public void ShowPlugins() { public void ShowPlugins() {
foreach (Plugin plugin in m_pluginList) { foreach (Plugin plugin in m_pluginList) {
//Ausgabe der Namen aller am Host registrierten Plug-Ins //Ausgabe der Namen aller am Host registrierten Plug-Ins
Console.WriteLine(plugin.Name); Console.WriteLine(plugin.Name);
} }
} }
22
public void UnloadPlugins() { public void UnloadPlugins() {
foreach (Plugin plugin in m_pluginList) { foreach (Plugin plugin in m_pluginList) {
//ruft implizit die IHost.Unregister-Methode auf //ruft implizit die IHost.Unregister-Methode auf
plugin.Host = null ; plugin.Host = null ;
} }
} }
29
#region IHost-Implementierung #region IHost-Implementierung
public bool Register(Plugin plugin) { public bool Register(Plugin plugin) {
if (!m_pluginList.Contains(plugin)) { if (!m_pluginList.Contains(plugin)) {
@@ -112,7 +105,6 @@ return true ;
} }
return false ; return false ;
} }
38
public bool Unregister(Plugin plugin) { public bool Unregister(Plugin plugin) {
if (m_pluginList.Contains(plugin)) { if (m_pluginList.Contains(plugin)) {
m_pluginList.Remove(plugin); m_pluginList.Remove(plugin);
+12 -22
View File
@@ -19,7 +19,7 @@ Für dieses Beispiel habe ich eine kleine Konsolen-Anwendung geschrieben (TestAp
**TestClass.cs** **TestClass.cs**
``` ```csharp
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
namespace TestProject { namespace TestProject {
@@ -51,7 +51,7 @@ return value;
**SecondTestClass.cs** **SecondTestClass.cs**
``` ```csharp
namespace TestProject { namespace TestProject {
public class SecondTestClass { public class SecondTestClass {
public int Addition( int a, int b) { public int Addition( int a, int b) {
@@ -66,8 +66,6 @@ return a / b;
} }
} }
} }
.
.
``` ```
## Auslesen von Assembly- Type-Informationen ## Auslesen von Assembly- Type-Informationen
@@ -76,7 +74,7 @@ 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: Um sich nun alle Assemblies der aktuellen Domäne anzeigen zu lassen, könnte man folgendes schreiben:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTest Class (); SecondTestClass secondTestClass = new SecondTest Class ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -88,11 +86,10 @@ Console .ReadLine();
} }
``` ```
.
Nun könnte man noch alle zur Verfügung stehenden Typen(Klassen) aus der TestProject-Assembly anzeigen. Nun könnte man noch alle zur Verfügung stehenden Typen(Klassen) aus der TestProject-Assembly anzeigen.
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass (); SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -110,11 +107,10 @@ 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: 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:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass (); SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -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: 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); MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
``` ```
@@ -148,7 +143,7 @@ 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: 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:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass (); SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -200,11 +195,10 @@ 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. 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.
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" ); Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) { if (assembly != null ) {
@@ -237,7 +231,7 @@ 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: 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:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" ); Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) { if (assembly != null ) {
@@ -263,11 +257,10 @@ Console .ReadLine();
Aufruf der Methode Addition 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. Bei statischen Methoden kann das Erzeugen der Instanz weggelassen werden und für den Parameter des Instanz-Objektes kann null übergeben werden.
``` ```csharp
if (type.Name.Equals( "TestClass" )) { if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name); Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods( BindingFlags .NonPublic | BindingFlags .Static); MethodInfo [] methodInfos = type.GetMethods( BindingFlags .NonPublic | BindingFlags .Static);
@@ -282,11 +275,10 @@ Console .WriteLine( " ++ {0}(\"myPassword\") = {1}" , info.Name, value);
Das übergebene Passwort wurde über eine statische Methode gehashed. 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. 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.
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"C:\ TestProject.dll" ); Assembly assembly = Assembly .LoadFrom( @"C:\ TestProject.dll" );
if (assembly != null ) { if (assembly != null ) {
@@ -308,11 +300,10 @@ 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. Wenn man nun die Properties mit Werten belegen möchte, so muss man analog zur Methode PropertyInfo.GetValue, die Methode PropertyInfo. SetValue verwenden.
``` ```csharp
if (type.Name.Equals( "TestClass" )) { if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name); Console .WriteLine( " ++ {0}" , type.Name);
PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic
@@ -326,7 +317,6 @@ Console .WriteLine( " ++ {0} = {1}" , info.Name, info.GetValue(obj, null ));
} }
``` ```
.
## Wozu braucht man jetzt aber nun die Reflection? ## Wozu braucht man jetzt aber nun die Reflection?
+4 -5
View File
@@ -28,7 +28,7 @@ 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. Die Nutzung selbst ist denkbar einfach. Folgendes Mitarbeiter-Objekt möchte ich speichern.
``` ```csharp
public class Mitarbeiter public class Mitarbeiter
{ {
public int Id { get ; set ; } public int Id { get ; set ; }
@@ -36,7 +36,6 @@ public string Name { get ; set ; }
public string Vorname { get ; set ; } public string Vorname { get ; set ; }
public string Ort { get ; set ; } public string Ort { get ; set ; }
public string Plz { get ; set ; } public string Plz { get ; set ; }
72
public string VollerName public string VollerName
{ {
get { return string .Format( {0} {1} , Vorname, Name); } get { return string .Format( {0} {1} , Vorname, Name); }
@@ -46,7 +45,7 @@ get { return string .Format( “{0} {1}” , Vorname, Name); }
Dazu nutzt man die Put-Methode der StupidDB. Dazu nutzt man die Put-Methode der StupidDB.
``` ```csharp
IStupidDB stupidDB = new StupidDB ( test ); IStupidDB stupidDB = new StupidDB ( test );
Mitarbeiter mitarbeiter = new Mitarbeiter Mitarbeiter mitarbeiter = new Mitarbeiter
{ {
@@ -61,7 +60,7 @@ stupidDB.Put( “TestPartition” , “EinMitarbeiter” , mitarbeiter );
Um das gleiche Objekt wieder zu lesen, nutzt man mal eine Get Methode. Die Parameter sind Partition und Key: Um das gleiche Objekt wieder zu lesen, nutzt man mal eine Get Methode. Die Parameter sind Partition und Key:
``` ```csharp
Mitarbeiter mitarbeiterAusgelesen = Mitarbeiter mitarbeiterAusgelesen =
( Mitarbeiter )stupidDB.GetObject( ( Mitarbeiter )stupidDB.GetObject(
TestPartition , TestPartition ,
@@ -77,7 +76,7 @@ mitarbeiterAusgelesen.VollerName);
Die StupidDB selbst wird über die app.config definiert. Die StupidDB selbst wird über die app.config definiert.
``` ```csharp
< configuration > < configuration >
< configSections > < 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 />
+5 -22
View File
@@ -18,17 +18,15 @@ Nehmen wir zum Beispiel ein einfaches Formular. Nach Absenden des Formulars soll
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. 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.
``` ```csharp
using System; using System;
using System.Web.UI.HtmlControls; using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls; using System.Web.UI.WebControls;
using framework.Testable.Web.UI; using framework.Testable.Web.UI;
5
namespace TestableWebForm namespace TestableWebForm
{ {
public class DefaultPage : System.Web.UI. Page public class DefaultPage : System.Web.UI. Page
{ {
10
#region Controls #region Controls
public HtmlForm Formular; public HtmlForm Formular;
public TextBox Value1 = new TextBox (); public TextBox Value1 = new TextBox ();
@@ -40,8 +38,7 @@ public Button Submit = new Button ();
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. 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.
``` ```csharp
1
namespace framework.Testable.Web namespace framework.Testable.Web
{ {
namespace UI namespace UI
@@ -52,31 +49,26 @@ bool IsPostBack{ get ; }
IHttpRequest Request{ get ; set ; } IHttpRequest Request{ get ; set ; }
IHttpResponse Response{ get ; set ; } IHttpResponse Response{ get ; set ; }
} }
12
public class Page : IPage public class Page : IPage
{ {
private readonly System.Web.UI. Page m_page; private readonly System.Web.UI. Page m_page;
private IHttpRequest m_request; private IHttpRequest m_request;
private IHttpResponse m_response; private IHttpResponse m_response;
18
public Page( System.Web.UI. Page page ) public Page( System.Web.UI. Page page )
{ {
m_page = page; m_page = page;
m_request = new HttpRequest ( m_page ); m_request = new HttpRequest ( m_page );
m_response = new HttpResponse ( 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; } get { return m_request; }
set { m_request = value ; } set { m_request = value ; }
} }
36
public IHttpResponse Response public IHttpResponse Response
{ {
get { return m_response; } get { return m_response; }
@@ -91,7 +83,7 @@ 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. Das war ungefähr alles: in der Seite nutzt man dann anstelle der eigenen Request und Response-Objekten die `Testable`-Objekte.
``` ```csharp
public class DefaultPage : System.Web.UI. Page public class DefaultPage : System.Web.UI. Page
{ {
@@ -100,7 +92,6 @@ public void SetTestableObjects( IPage page )
{ {
m_page = 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 ); if (m_page == null ) m_page = new Page ( this );
@@ -115,13 +106,12 @@ Result.Text = ( ( Convert .ToInt32( Value1.Text ) + Convert .ToInt32( Value2.Te
Damit ist die Web-Anwendung bereit zum Testen. So schaut zum Beispiel ein Test für das Laden der Seite aus: Damit ist die Web-Anwendung bereit zum Testen. So schaut zum Beispiel ein Test für das Laden der Seite aus:
``` ```csharp
using framework.Testable.Web; using framework.Testable.Web;
using framework.Testable.Web.UI; using framework.Testable.Web.UI;
using NUnit.Framework; using NUnit.Framework;
using Rhino.Mocks; using Rhino.Mocks;
using TestableWebForm; using TestableWebForm;
6
namespace Tests namespace Tests
{ {
[ TestFixture ] [ TestFixture ]
@@ -131,7 +121,6 @@ private IPage m_page;
private IHttpRequest m_request; private IHttpRequest m_request;
private IHttpResponse m_response; private IHttpResponse m_response;
private DefaultPage m_defaultPage; private DefaultPage m_defaultPage;
16
[ SetUp ] [ SetUp ]
public void Init() public void Init()
{ {
@@ -141,18 +130,14 @@ m_response = MockRepository .GenerateStub< IHttpResponse >();
m_page.Request = m_request; m_page.Request = m_request;
m_page.Response = m_response; m_page.Response = m_response;
} }
26
[ Test ] [ Test ]
public void PageLoad_Loading_EmptyFields() public void PageLoad_Loading_EmptyFields()
{ {
// Arrange // Arrange
31
32
// Act // Act
m_defaultPage = new DefaultPage (); m_defaultPage = new DefaultPage ();
m_defaultPage.SetTestableObjects(m_page); m_defaultPage.SetTestableObjects(m_page);
m_defaultPage.Page_Load( null , null ); m_defaultPage.Page_Load( null , null );
37
// Assert // Assert
Assert .IsEmpty( m_defaultPage.Value1.Text ); Assert .IsEmpty( m_defaultPage.Value1.Text );
Assert .IsEmpty( m_defaultPage.Value2.Text ); Assert .IsEmpty( m_defaultPage.Value2.Text );
@@ -162,7 +147,7 @@ Assert .IsEmpty(m_defaultPage.Result.Text);
Und so für PostBack inklusive QueryString-Parameter: Und so für PostBack inklusive QueryString-Parameter:
``` ```csharp
[ Test ] [ Test ]
public void PageLoad_PostBackWithRequestValue_ResultIsCorrect() public void PageLoad_PostBackWithRequestValue_ResultIsCorrect()
{ {
@@ -172,14 +157,12 @@ const int value2 = 2;
const int value3 = 3; const int value3 = 3;
m_request.Expect( a => a.GetParamValue( "multiple" ) ).IgnoreArguments().Repeat.Twice().Return( value3.ToString() ); m_request.Expect( a => a.GetParamValue( "multiple" ) ).IgnoreArguments().Repeat.Twice().Return( value3.ToString() );
m_page.Expect( a => a.IsPostBack ).Return( true ); m_page.Expect( a => a.IsPostBack ).Return( true );
72
// Act // Act
m_defaultPage = new DefaultPage (); m_defaultPage = new DefaultPage ();
m_defaultPage.SetTestableObjects( m_page ); m_defaultPage.SetTestableObjects( m_page );
m_defaultPage.Value1.Text = value1.ToString(); m_defaultPage.Value1.Text = value1.ToString();
m_defaultPage.Value2.Text = value2.ToString(); m_defaultPage.Value2.Text = value2.ToString();
m_defaultPage.Page_Load( null , null ); m_defaultPage.Page_Load( null , null );
79
// Assert // Assert
Assert .IsTrue( m_defaultPage.Result.Text == ( (value1 + value2)*value3 ).ToString() ); Assert .IsTrue( m_defaultPage.Result.Text == ( (value1 + value2)*value3 ).ToString() );
} }