mirror of
https://github.com/skoelle/buildbroken-blog-archive.git
synced 2026-09-18 10:50:25 +00:00
Compare commits
16
Commits
b757ce3406
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d36d13b44e | ||
|
|
75eb7cc749 | ||
|
|
2e307dcc86 | ||
|
|
cd8a75ec13 | ||
|
|
2dc30200a1 | ||
|
|
92248b5ad7 | ||
|
|
646b30d91d | ||
|
|
abc6c54cf6 | ||
|
|
6b1dfd43d3 | ||
|
|
b6345be087 | ||
|
|
83282a00a1 | ||
|
|
df465d34df | ||
|
|
15fdec825a | ||
|
|
4c697f1928 | ||
|
|
2452b7e108 | ||
|
|
9bf9059f39 |
@@ -24,7 +24,8 @@ final; nichts neu herunterladen.
|
|||||||
gebaut sein (Links werden aus `public/` gelesen). `replacement`-Einträge
|
gebaut sein (Links werden aus `public/` gelesen). `replacement`-Einträge
|
||||||
und manuell kuratierte `broken`-Einträge werden nie automatisch überschrieben;
|
und manuell kuratierte `broken`-Einträge werden nie automatisch überschrieben;
|
||||||
403/429 (Bot-Schutz) und DNS/Timeout werden nicht als broken markiert.
|
403/429 (Bot-Schutz) und DNS/Timeout werden nicht als broken markiert.
|
||||||
- **Content** in `content/posts/*.md` und `content/_index.md` wird von Hand
|
- **Content** in `content/posts/*.md`, `content/_index.md`,
|
||||||
|
`content/categories/*/_index.md` und `content/tags/*/_index.md` wird von Hand
|
||||||
gepflegt. Titel sollen unter ~60 Zeichen bleiben.
|
gepflegt. Titel sollen unter ~60 Zeichen bleiben.
|
||||||
- **SEO-Metadaten** werden zentral in `layouts/_default/baseof.html` erzeugt
|
- **SEO-Metadaten** werden zentral in `layouts/_default/baseof.html` erzeugt
|
||||||
(description/canonical/OG/Twitter). Keine Per-Post-Hardcodes außer nötig.
|
(description/canonical/OG/Twitter). Keine Per-Post-Hardcodes außer nötig.
|
||||||
|
|||||||
@@ -1,161 +1,240 @@
|
|||||||
# buildbroken-blog-archive
|
# 🏗️ buildbroken-blog-archive
|
||||||
|
|
||||||
Statisches Archiv des ehemaligen ".NET/build broken"-Blogs (`aztec-project.org/blog/`),
|
Statisches Archiv des ehemaligen **".NET/build broken"-Blogs** (`aztec-project.org/blog/`),
|
||||||
aufgebaut mit [Hugo](https://gohugo.io). Alle Inhalte stammen aus web.archive.org
|
aufgebaut mit [Hugo](https://gohugo.io). Alle Inhalte stammen aus web.archive.org
|
||||||
Downloads und werden lokal gehostet (Bilder, CSS, Content).
|
Downloads und werden lokal gehostet (Bilder, CSS, Content).
|
||||||
|
|
||||||
Die Blogartikel unter `content/posts/` und die zugehörigen Bilder wurden
|
> 💾 Die Blogartikel unter `content/posts/` und die zugehörigen Bilder wurden
|
||||||
ursprünglich von den Autoren von "build broken" (aztec-project.org, 2009–2010)
|
> ursprünglich von den Autoren von "build broken" (aztec-project.org, 2009–2010)
|
||||||
geschrieben und werden hier ausschließlich zu persönlichen Archivierungszwecken
|
> geschrieben und werden hier ausschließlich zu persönlichen Archivierungszwecken
|
||||||
aufbewahrt. Das Urheberrecht verbleibt bei den ursprünglichen Autoren; die
|
> aufbewahrt. Das Urheberrecht verbleibt bei den ursprünglichen Autoren; die
|
||||||
MIT-Lizenz dieses Repositories gilt nur für den Code (Parser-Skripte,
|
> MIT-Lizenz dieses Repositories gilt nur für den Code (Parser-Skripte,
|
||||||
Hugo-Templates und CSS), nicht für die archivierten Bloginhalte selbst.
|
> Hugo-Templates und CSS), nicht für die archivierten Bloginhalte selbst.
|
||||||
|
|
||||||
## URL-Struktur
|
---
|
||||||
|
|
||||||
| Bereich | Alte URL (Wayback) | Neue lokale URL |
|
## 🔗 URL-Struktur
|
||||||
|
|
||||||
|
| 🏷️ Bereich | 🌐 Alte URL (Wayback) | 📍 Neue lokale URL |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Startseite (inkl. About-Text) | `blog/blog.html` | `/` |
|
| Startseite (inkl. About-Text) | `blog/blog.html` | `/` |
|
||||||
| About | `blog/about.html` | `/` (Text liegt auf der Startseite) |
|
| About | `blog/about.html` | `/` (Text liegt auf der Startseite) |
|
||||||
| Blogartikel | `blog/<slug>.html` | `/posts/<slug>/` |
|
| Blogartikel | `blog/<slug>.html` | `/posts/<slug>/` |
|
||||||
| Bilder | extern/beliebig | `/images/<slug>/<dateiname>` |
|
| 🖼️ Bilder | extern/beliebig | `/images/<slug>/<dateiname>` |
|
||||||
| CSS | Theme-CSS | `/css/style.css` |
|
| 🎨 CSS | Theme-CSS | `/css/style.css` |
|
||||||
|
|
||||||
`<slug>` ist der ursprüngliche Dateiname ohne `.html`, durch `python-slugify`
|
`<slug>` ist der ursprüngliche Dateiname ohne `.html`, durch `python-slugify`
|
||||||
normalisiert (Kleinschreibung, Bindestriche). Beispiel:
|
normalisiert (Kleinschreibung, Bindestriche). Beispiel:
|
||||||
`dotnet-openspace-leipzig-2009-buchung.html` -> `/posts/dotnet-openspace-leipzig-2009-buchung/`.
|
`dotnet-openspace-leipzig-2009-buchung.html` → `/posts/dotnet-openspace-leipzig-2009-buchung/`.
|
||||||
|
|
||||||
Interne Links zwischen Artikeln werden vom Parser automatisch von
|
🔗 Interne Links zwischen Artikeln werden vom Parser automatisch von
|
||||||
`https://aztec-project.org/blog/xyz.html` auf `/posts/xyz/` umgeschrieben.
|
`https://aztec-project.org/blog/xyz.html` auf `/posts/xyz/` umgeschrieben.
|
||||||
|
|
||||||
## Voraussetzungen
|
---
|
||||||
|
|
||||||
- [Hugo](https://gohugo.io/installation/) (extended oder normal reicht, kein Node nötig)
|
## 🛠️ Voraussetzungen
|
||||||
- Python 3.10+
|
|
||||||
- `pip install -r scripts/requirements.txt`
|
|
||||||
|
|
||||||
## Ordnerstruktur
|
- 🐢 [Hugo](https://gohugo.io/installation/) (extended oder normal reicht, kein Node nötig)
|
||||||
|
- 🐍 Python 3.10+
|
||||||
|
- 📦 `pip install -r scripts/requirements.txt`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Ordnerstruktur
|
||||||
|
|
||||||
```
|
```
|
||||||
buildbroken-blog-archive/
|
buildbroken-blog-archive/
|
||||||
├── hugo.toml # Hugo-Konfiguration
|
├── hugo.toml # ⚙️ Hugo-Konfiguration
|
||||||
├── content/
|
├── content/
|
||||||
│ ├── _index.md # Startseite (About-Text liegt hier, kein eigenes /about/)
|
│ ├── _index.md # 🏠 Startseite (About-Text liegt hier, kein eigenes /about/)
|
||||||
│ └── posts/ # wird vom Parser mit *.md befüllt
|
│ ├── categories/ # 📂 pro Kategorie eine _index.md mit Einleitungstext
|
||||||
├── layouts/ # eigenes Theme, angelehnt an das Original-Design
|
│ ├── tags/ # 🏷️ pro Tag eine _index.md mit Einleitungstext
|
||||||
|
│ └── posts/ # 📝 wird vom Parser mit *.md befüllt
|
||||||
|
├── layouts/ # 🎨 eigenes Theme, angelehnt an das Original-Design
|
||||||
│ ├── _default/baseof.html
|
│ ├── _default/baseof.html
|
||||||
│ ├── _default/single.html
|
│ ├── _default/single.html
|
||||||
│ ├── _default/list.html
|
│ ├── _default/list.html
|
||||||
|
│ ├── _default/term.html
|
||||||
│ └── index.html
|
│ └── index.html
|
||||||
├── static/
|
├── static/
|
||||||
│ ├── css/style.css
|
│ ├── css/style.css
|
||||||
│ └── images/<slug>/... # wird vom Parser befüllt
|
│ └── images/<slug>/... # 🖼️ wird vom Parser befüllt
|
||||||
├── archive/
|
├── archive/
|
||||||
│ └── wayback-html/ # HIER die heruntergeladenen Wayback-HTML-Dateien ablegen
|
│ └── wayback-html/ # 📥 HIER die heruntergeladenen Wayback-HTML-Dateien ablegen
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── parse_buildbroken_archive.py
|
│ ├── parse_buildbroken_archive.py
|
||||||
│ └── requirements.txt
|
│ └── requirements.txt
|
||||||
├── URLS.md # Liste aller zu parsenden Quell-URLs/Dateien
|
├── URLS.md # 📋 Liste aller zu parsenden Quell-URLs/Dateien
|
||||||
└── TODO.md # Arbeitsauftrag für die lokale KI
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ablauf
|
---
|
||||||
|
|
||||||
1. Alle Dateien aus `URLS.md` als HTML von web.archive.org herunterladen und
|
## 🚀 Ablauf
|
||||||
unter `archive/wayback-html/<dateiname>.html` ablegen (exakt gleicher Name).
|
|
||||||
2. Parser laufen lassen:
|
|
||||||
```bash
|
|
||||||
cd scripts
|
|
||||||
pip install -r requirements.txt
|
|
||||||
python parse_buildbroken_archive.py --source ../archive/wayback-html --output .. --download-images
|
|
||||||
```
|
|
||||||
> **Wichtig:** Der Parser soll **nicht mehr erneut ausgeführt** werden. Die
|
|
||||||
> generierten Artikel in `content/posts/*.md` wurden nachträglich manuell
|
|
||||||
> ausgebessert (Titel gekürzt, Tags korrigiert, `alt`-Attribute ergänzt,
|
|
||||||
> Links umgeschrieben). Ein erneuter Lauf würde diese Handarbeit überschreiben.
|
|
||||||
3. Lokale Vorschau:
|
|
||||||
```bash
|
|
||||||
cd ..
|
|
||||||
hugo server -D --bind 0.0.0.0 --baseURL http://<hostname>:1313/
|
|
||||||
```
|
|
||||||
`<hostname>` durch den Hostnamen des Rechners ersetzen (z. B. `xubuntu-dev.lan`).
|
|
||||||
4. Produktions-Build:
|
|
||||||
```bash
|
|
||||||
hugo --minify
|
|
||||||
```
|
|
||||||
Ergebnis liegt in `public/` und kann auf beliebigem statischen Hosting
|
|
||||||
(nginx-Container, Gitea Pages, GitHub Pages) deployt werden.
|
|
||||||
|
|
||||||
## Link-Status (tote Links kennzeichnen)
|
### 1⃣ HTML herunterladen
|
||||||
|
|
||||||
Externe Links in Artikeln und Kommentaren werden in `data/links.json` gepflegt
|
Alle Dateien aus `URLS.md` als HTML von web.archive.org herunterladen und
|
||||||
|
unter `archive/wayback-html/<dateiname>.html` ablegen (exakt gleicher Name).
|
||||||
|
|
||||||
|
### 2⃣ Parser laufen lassen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd scripts
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python parse_buildbroken_archive.py --source ../archive/wayback-html --output .. --download-images
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **Wichtig:** Der Parser soll **nicht mehr erneut ausgeführt** werden. Die
|
||||||
|
> generierten Artikel in `content/posts/*.md` wurden nachträglich manuell
|
||||||
|
> ausgebessert (Titel gekürzt, Tags korrigiert, `alt`-Attribute ergänzt,
|
||||||
|
> Links umgeschrieben. Ein erneuter Lauf würde diese Handarbeit überschreiben.
|
||||||
|
|
||||||
|
### 3⃣ Lokale Vorschau
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ..
|
||||||
|
hugo server -D --bind 0.0.0.0 --baseURL http://<hostname>:1313/
|
||||||
|
```
|
||||||
|
|
||||||
|
`<hostname>` durch den Hostnamen des Rechners ersetzen (z. B. `xubuntu-dev.lan`).
|
||||||
|
|
||||||
|
### 4⃣ Produktions-Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hugo --minify
|
||||||
|
```
|
||||||
|
|
||||||
|
🌍 Ergebnis liegt in `public/` und kann auf beliebigem statischen Hosting
|
||||||
|
(nginx-Container, Gitea Pages, GitHub Pages) deployt werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Link-Status (tote Links kennzeichnen)
|
||||||
|
|
||||||
|
🔗 Externe Links in Artikeln und Kommentaren werden in `data/links.json` gepflegt
|
||||||
und clientseitig (`static/js/link-status.js`) verarbeitet:
|
und clientseitig (`static/js/link-status.js`) verarbeitet:
|
||||||
|
|
||||||
- **`broken`**: Die URL wird im Text normal angezeigt, aber beim Klick öffnet
|
- 🚫 **`broken`**: Die URL wird im Text normal angezeigt, aber beim Klick öffnet
|
||||||
sich ein kleines Modal "Diese URL scheint nicht mehr verfügbar zu sein" mit
|
sich ein kleines Modal "Diese URL scheint nicht mehr verfügbar zu sein" mit
|
||||||
einem Button zur Web-Archive-Version (`https://web.archive.org/web/*/<url>`).
|
einem Button zur Web-Archive-Version (`https://web.archive.org/web/*/<url>`).
|
||||||
- **`replacement`**: Die URL wird automatisch auf eine nachgewiesene
|
- 🔄 **`replacement`**: Die URL wird automatisch auf eine nachgewiesene
|
||||||
Nachfolge-URL umgeschrieben (z. B. `codinghorror.com` → `blog.codinghorror.com`).
|
Nachfolge-URL umgeschrieben (z. B. `codinghorror.com` → `blog.codinghorror.com`).
|
||||||
|
|
||||||
|
### 🔧 Link-Checker
|
||||||
|
|
||||||
Der Link-Checker `scripts/check_links.py` prüft alle tatsächlich gerenderten
|
Der Link-Checker `scripts/check_links.py` prüft alle tatsächlich gerenderten
|
||||||
`<a href>`-Links und aktualisiert `data/links.json`:
|
`<a href>`-Links und aktualisiert `data/links.json`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.venv/bin/python scripts/check_links.py # prüfen + data/links.json aktualisieren
|
.venv/bin/python scripts/check_links.py # 🔍 prüfen + data/links.json aktualisieren
|
||||||
.venv/bin/python scripts/check_links.py --check-only # nur prüfen, nichts schreiben
|
.venv/bin/python scripts/check_links.py --check-only # 👀 nur prüfen, nichts schreiben
|
||||||
```
|
```
|
||||||
|
|
||||||
Verhalten des Checkers:
|
### ⚙️ Verhalten des Checkers
|
||||||
|
|
||||||
- Er liest die Links aus dem **gebauten** `public/` (vorher `hugo --minify`),
|
- 📖 Er liest die Links aus dem **gebauten** `public/` (vorher `hugo --minify`),
|
||||||
damit nur echte Links erfasst werden (Code-Beispiele/Platzhalter ignoriert).
|
damit nur echte Links erfasst werden (Code-Beispiele/Platzhalter ignoriert).
|
||||||
- HTTP 403/429 (Bot-Schutz) und DNS-/Timeout-Fehler (ERR) werden **nicht**
|
- 🛡️ HTTP 403/429 (Bot-Schutz) und DNS-/Timeout-Fehler (ERR) werden **nicht**
|
||||||
als neu broken gemeldet; bestehende broken-Einträge bleiben erhalten,
|
als neu broken gemeldet; bestehende broken-Einträge bleiben erhalten,
|
||||||
solange die URL nicht eindeutig wieder mit 200 antwortet.
|
solange die URL nicht eindeutig wieder mit 200 antwortet.
|
||||||
- Manuelle `replacement`-Einträge werden nie überschrieben.
|
- ✏️ Manuelle `replacement`-Einträge werden nie überschrieben.
|
||||||
- Das Skript ist idempotent: ein Lauf ohne Änderungen meldet "0 neu / 0 entfernt".
|
- 🔄 Das Skript ist idempotent: ein Lauf ohne Änderungen meldet "0 neu / 0 entfernt".
|
||||||
|
|
||||||
## Hosting (Cloudflare Pages, migriert in Workers)
|
---
|
||||||
|
|
||||||
|
## ☁️ Hosting (Cloudflare Pages, migriert in Workers)
|
||||||
|
|
||||||
Das Projekt wird über die Cloudflare-Pages-**Git-Integration** deployed
|
Das Projekt wird über die Cloudflare-Pages-**Git-Integration** deployed
|
||||||
(Pages wurde inzwischen in den Workers-Bereich migriert, das Prinzip ist
|
(Pages wurde inzwischen in den Workers-Bereich migriert, das Prinzip ist
|
||||||
unverändert):
|
unverändert):
|
||||||
|
|
||||||
1. **GitHub-Repo** anlegen und dieses Projekt hineinpushen.
|
1. 📦 **GitHub-Repo** anlegen und dieses Projekt hineinpushen.
|
||||||
2. In Cloudflare unter **Workers & Pages → Create application → Pages** den
|
2. ☁️ In Cloudflare unter **Workers & Pages → Create application → Pages** den
|
||||||
Tab **Import an existing Git repository** wählen und das Repo auswählen.
|
Tab **Import an existing Git repository** wählen und das Repo auswählen.
|
||||||
3. Build-Einstellungen hinterlegen:
|
3. ⚙️ Build-Einstellungen hinterlegen:
|
||||||
- **Framework preset:** Hugo
|
- **Framework preset:** Hugo
|
||||||
- **Build command (Bereitstellungsbefehl):** `hugo --minify`
|
- **Build command:** `hugo --minify`
|
||||||
Ein eigenes **Build output directory** kann bei Cloudflare Pages nicht
|
- Ein eigenes **Build output directory** kann bei Cloudflare Pages nicht
|
||||||
angegeben werden — es kommt aus der Hugo-Konfiguration (`publishDir` in
|
angegeben werden — es kommt aus der Hugo-Konfiguration (`publishDir` in
|
||||||
`hugo.toml`, Standard `public`).
|
`hugo.toml`, Standard `public`).
|
||||||
4. Speichern. Es ist **kein Deploy-Befehl nötig**: Jeder Push auf den
|
4. 💾 Speichern. Es ist **kein Deploy-Befehl nötig**: Jeder Push auf den
|
||||||
verbundenen GitHub-Branch (z. B. `main`) baut und deployed automatisch.
|
verbundenen GitHub-Branch (z. B. `main`) baut und deployed automatisch.
|
||||||
|
|
||||||
Die `wrangler.json` im Projekt-Root konfiguriert das Deployment:
|
### ⚙️ wrangler.json
|
||||||
- `name`: Projektname (`buildbroken-blog-archive`)
|
|
||||||
- `compatibility_date`: Datum der Cloudflare-Laufzeit
|
|
||||||
- `assets.directory`: `public` — das Hugo-Ausgabeverzeichnis, das bereitgestellt wird
|
|
||||||
- `assets.not_found_handling`: `404-page` — liefert die eigene `public/404.html` (statt eines generischen 404) für nicht gefundene Pfade
|
|
||||||
|
|
||||||
## SEO
|
Die `wrangler.json` im Projekt-Root konfiguriert das Deployment:
|
||||||
|
|
||||||
|
| Parameter | Wert | Beschreibung |
|
||||||
|
|---|---|---|
|
||||||
|
| `name` | `buildbroken-blog-archive` | 🏷️ Projektname |
|
||||||
|
| `compatibility_date` | Datum | 📅 Cloudflare-Laufzeit |
|
||||||
|
| `assets.directory` | `public` | 📂 Hugo-Ausgabeverzeichnis |
|
||||||
|
| `assets.not_found_handling` | `404-page` | 🔀 Eigene 404.html statt generischem 404 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 SEO
|
||||||
|
|
||||||
Das Layout erzeugt für jede Seite automatisch SEO-Metadaten (zentral in
|
Das Layout erzeugt für jede Seite automatisch SEO-Metadaten (zentral in
|
||||||
`layouts/_default/baseof.html`):
|
`layouts/_default/baseof.html`):
|
||||||
|
|
||||||
- `meta name="description"` (aus Frontmatter/`.Description`, sonst aus `.Summary`; für Taxonomie-, Autor- und Archiv-Seiten generisch formuliert, auf ~160 Zeichen gekürzt)
|
- 📝 `meta name="description"` (aus Frontmatter/`.Description`, sonst aus `.Summary`)
|
||||||
- `link rel="canonical"` mit der absoluten URL
|
- 🔗 `link rel="canonical"` mit der absoluten URL
|
||||||
- Open-Graph- und Twitter-Card-Tags (`og:type` article/website, `og:image` Banner, `twitter:card` summary_large_image)
|
- 🐦 Open-Graph- und Twitter-Card-Tags (`og:type`, `og:image`, `twitter:card`)
|
||||||
- `<title>`: Tag- und Kategorie-Seiten werden differenziert (`MVC (Tag)` vs. `MVC (Kategorie)`), Länge auf ~60 Zeichen begrenzt
|
- 🏷️ `<title>`: Tag- und Kategorie-Seiten werden differenziert (`MVC (Tag)` vs. `MVC (Kategorie)`), Länge auf ~60 Zeichen begrenzt
|
||||||
|
|
||||||
Außerdem:
|
### 🏠 Startseite
|
||||||
|
|
||||||
- Eigene `layouts/_default/404.html`, die via `assets.not_found_handling: "404-page"` von Cloudflare ausgeliefert wird
|
Für die Startseite wird ein eigener `<title>` über `params.homeTitle` in
|
||||||
- `robots.txt` verweist auf `sitemap.xml` (Hugo-generiert)
|
`hugo.toml` konfiguriert (statt nur dem bloginfo `title`). Die Description
|
||||||
- Content-Bilder in `content/posts/*.md` tragen `alt`-Attribute; der dekorative Banner hat bewusst leeres `alt=""`
|
der Startseite kommt aus `params.description`.
|
||||||
|
|
||||||
## Layout anpassen
|
### ✅ Zusätzlich
|
||||||
|
|
||||||
|
- 🔀 Eigene `layouts/_default/404.html`, die via `assets.not_found_handling` von Cloudflare ausgeliefert wird
|
||||||
|
- 🤖 `robots.txt` verweist auf `sitemap.xml` (Hugo-generiert)
|
||||||
|
- 🖼️ Content-Bilder in `content/posts/*.md` tragen `alt`-Attribute; der dekorative Banner hat bewusst leeres `alt=""`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Kategorien & Tags
|
||||||
|
|
||||||
|
Kategorie- und Tag-Seiten können individuelle Einleitungstexte haben. Dafür
|
||||||
|
legt man in `content/categories/<name>/_index.md` bzw. `content/tags/<name>/_index.md`
|
||||||
|
eine Datei mit Front Matter und Body-Text an:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: "Clean Code Developing"
|
||||||
|
---
|
||||||
|
|
||||||
|
Die Grundlage unserer Arbeit: SOLID, DRY, Refactoring und professionelle Software-Entwicklung.
|
||||||
|
```
|
||||||
|
|
||||||
|
Der `title` in der Front Matter bestimmt die `<h2>`-Überschrift auf der Seite.
|
||||||
|
**Wichtig:** Die Groß-/Kleinschreibung muss korrekt sein (z. B. `C#`, `MVC`,
|
||||||
|
`Visual Studio`), da Hugo den slug automatisch kleinschreibt (URL: `/tags/c#/`).
|
||||||
|
|
||||||
|
Der Body-Text wird als `<div class="archive-intro">` zwischen Überschrift und
|
||||||
|
Artikelliste gerendert (via `layouts/_default/term.html`). Ohne Body-Text
|
||||||
|
wird kein `<div>` generiert — die Seiten funktionieren auch ohne Einleitungstexte.
|
||||||
|
|
||||||
|
**Aktuelle Kategorien (17):** Architektur, BarCamp, Clean Code Developing, How-To,
|
||||||
|
MVC, Open Space, Pattern, prio.conference, Release, Scrum, StupidDB, Tipps,
|
||||||
|
Unit Testing, Veranstaltung, Visual Studio Addins, Webanwendungen, Webservice.
|
||||||
|
|
||||||
|
**Aktuelle Tags (43):** Addin, Architektur, ASP-Classic, Assemblies, Assembly,
|
||||||
|
Basics, Buch, Buchempfehlung, C#, CCD, Clean Code, Coding, Community,
|
||||||
|
Contract-First, Datenbank, Datenschutz, Facebook, HowTo, HTML, MVC, MVP,
|
||||||
|
NoSQL, Open Space, Patterns, Plug-In, prio.conference, Refaktorisierung,
|
||||||
|
Reflection, Schemalos, SCRUM, Social Web, Sonne, Sourcecode, StupidDB,
|
||||||
|
Unit Tests, Veranstaltung, Visual Studio, Webapplication, WebCamp, WebCast,
|
||||||
|
Webservice, WinForms, Wordpress.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Layout anpassen
|
||||||
|
|
||||||
Alle Design-Änderungen passieren zentral in `layouts/_default/*.html` und
|
Alle Design-Änderungen passieren zentral in `layouts/_default/*.html` und
|
||||||
`static/css/style.css`. Nach einer Änderung reicht `hugo --minify`, um alle
|
`static/css/style.css`. Nach einer Änderung reicht `hugo --minify`, um alle
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# TODO.md
|
||||||
|
|
||||||
|
Offene Punkte und Ideen für zukünftige Verbesserungen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hugo-Upgrade
|
||||||
|
|
||||||
|
- [ ] `.Site.Data` → `hugo.Data` in `baseof.html:110` anpassen (Deprecation-Warnung bei jedem Build)
|
||||||
|
|
||||||
|
## Content
|
||||||
|
|
||||||
|
- [x] ~~Ggf. fehlende `alt`-Attribute bei Bildern in Posts prüfen~~ — alles OK, Smiley-Icons auf `alt=""` bereinigt
|
||||||
|
|
||||||
|
## SEO / Metadaten
|
||||||
|
|
||||||
|
- [x] ~~`og:description` für Kategorie-/Tag-Seiten prüfen~~ — `.Content` aus `_index.md` wird jetzt als Meta-Description verwendet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Zuletzt aktualisiert: 2026-08-22*
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: "Autoren"
|
||||||
|
---
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: "Kategorien"
|
||||||
|
---
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Architektur"
|
||||||
|
---
|
||||||
|
|
||||||
|
Von Softwarezellen über Design by Contract bis zu asynchronen Kommunikationsmuster — hier dreht sich alles um Struktur, Verträge und den klaren Aufbau komplexer Software.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "BarCamp"
|
||||||
|
---
|
||||||
|
|
||||||
|
Unsere Eindrücke und Berichte von BarCamps — von dezentralen sozialen Web-Protokollen bis hin zu Datenschutz-Diskussionen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Clean Code Developing"
|
||||||
|
---
|
||||||
|
|
||||||
|
Die Grundlage unserer Arbeit: SOLID, DRY, Refactoring und professionelle Software-Entwicklung. Von der Theorie bis zur Praxis im Alltag — hier sammeln wir alles rund um den Clean Code Developer.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "How-To"
|
||||||
|
---
|
||||||
|
|
||||||
|
Praktische Anleitungen für den Alltag: Reflection, dynamisches Assembly-Laden, Plug-In-Architekturen, anonyme Datentypen und Testbarkeit in .NET.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "MVC"
|
||||||
|
---
|
||||||
|
|
||||||
|
Erfahrungsberichte und Implementierungsbeispiele zu ASP.NET MVC und dem Model-View-Presenter-Pattern in WinForms.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Open Space"
|
||||||
|
---
|
||||||
|
|
||||||
|
Eindrücke von .NET Open Spaces in Leipzig und Ulm — unkonferenzartige Formate mit intensiven Diskussionen und direktem Austausch in der Community.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Pattern"
|
||||||
|
---
|
||||||
|
|
||||||
|
Entwurfsmuster in der Praxis — hier vor allem das Async-Pattern für saubere asynchrone Kommunikation in .NET-Anwendungen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "prio.conference"
|
||||||
|
---
|
||||||
|
|
||||||
|
Tagungsbericht von der prio.conference 2009 in München — mit Sessions zu UI-Entwicklung, Usability und agiler Softwareentwicklung.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Release"
|
||||||
|
---
|
||||||
|
|
||||||
|
Vorstellung unseres Open-Source-Frameworks StupidDB zur einfachen Objektpersistierung auf dem Dateisystem.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Scrum"
|
||||||
|
---
|
||||||
|
|
||||||
|
Reflexion über unsere eigene SCRUM-Einführung — was lief gut, was lief anders, und warum „Scrumbut" kein Schimpfwort ist.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "StupidDB"
|
||||||
|
---
|
||||||
|
|
||||||
|
StupidDB — unser schemaloser Objektspeicher für .NET. Von der ersten Idee über die Implementierung bis hin zu NoSQL-Konzepten.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Tipps"
|
||||||
|
---
|
||||||
|
|
||||||
|
Nützliche Empfehlungen und konkrete Tipps — von hilfreichen WebCasts bis zu Mustern für asynchrone Kommunikation.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Unit Testing"
|
||||||
|
---
|
||||||
|
|
||||||
|
Strategien und Workarounds für Unit-Tests in der Praxis — insbesondere bei nicht-öffentlichen Methoden und in WebForms-Umgebungen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Veranstaltung"
|
||||||
|
---
|
||||||
|
|
||||||
|
Berichte von Veranstaltungen und Workshops — wie dem zweitägigen Microsoft WebCamp in München zu MVC2, Entity Framework und jQuery.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Visual Studio Addins"
|
||||||
|
---
|
||||||
|
|
||||||
|
Nützliche Tools und Add-ins für Visual Studio — wie „Copy Source As HTML" für formatierte Code-Übertragung in Dokumentation und Blogs.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Webanwendungen"
|
||||||
|
---
|
||||||
|
|
||||||
|
Ansätze zur Testbarkeit und Architektur von ASP.NET WebForms — mit Schichtenarchitektur und Workarounds für bessere Unit-Tests.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Webservice"
|
||||||
|
---
|
||||||
|
|
||||||
|
Technische Anleitungen zur SOAP-Kommunikation — wie der sichere Austausch zwischen ASP-Classic und .NET-Webservices funktioniert.
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: "Impressum (§ 5 DDG)"
|
title: "Impressum (§ 5 DDG)"
|
||||||
layout: impressum
|
layout: impressum
|
||||||
|
description: "Impressum und Kontaktdaten des build broken Blog-Archivs: Stefan Kölle, München."
|
||||||
---
|
---
|
||||||
|
|
||||||
**Stefan Kölle**<br>
|
**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.
|
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/)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: "Beiträge"
|
||||||
|
---
|
||||||
@@ -18,8 +18,6 @@ Was wohl langfristig nötig ist, dass wir uns über den Grundgedanken von ALT.NE
|
|||||||
|
|
||||||
Seht ihr ähnliche Tendenzen wie Jeremy in der deutschen Community?
|
Seht ihr ähnliche Tendenzen wie Jeremy in der deutschen Community?
|
||||||
|
|
||||||
<img src="/images/alt-net-energien/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **Christina Hirth**
|
1. **Christina Hirth**
|
||||||
@@ -39,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.
|
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**
|
3. **Stefan Kölle**
|
||||||
|
|
||||||
[August 31, 2009 um 7:28 pm](/posts/alt-net-energien/)
|
[August 31, 2009 um 7:28 pm](/posts/alt-net-energien/)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,139 +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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
```
|
||||||
<img src="/images/async-refactored/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
@@ -157,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
-117
@@ -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,121 +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 }
|
|
||||||
|
|
||||||
<img src="/images/async/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## 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/)**
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ Der Code, den ein Entwickler schreibt, gehört meistens nicht ihm. Ich bin siche
|
|||||||
|
|
||||||
Nimmt eure Arbeit ernst und werdet ein professioneller Entwickler. Ihr werdet nach kürzester Zeit die Freude an einem korrekten und qualitativen Code entdecken und auch eure Teamkollegen und Chefs werden dafür dankbar sein.
|
Nimmt eure Arbeit ernst und werdet ein professioneller Entwickler. Ihr werdet nach kürzester Zeit die Freude an einem korrekten und qualitativen Code entdecken und auch eure Teamkollegen und Chefs werden dafür dankbar sein.
|
||||||
|
|
||||||
<img src="/images/clean-code-developer-yellow-brick-road/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **[build broken » Clean Code Teil 1](/posts/cleancode1/)**
|
1. **[build broken » Clean Code Teil 1](/posts/cleancode1/)**
|
||||||
|
|||||||
@@ -94,8 +94,6 @@ Clean Tests folgen noch 5 anderen Regeln:
|
|||||||
|
|
||||||
Teil 2 gibt es [hier](/posts/cleancode2/)
|
Teil 2 gibt es [hier](/posts/cleancode2/)
|
||||||
|
|
||||||
<img src="/images/cleancode1/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **Christina**
|
1. **Christina**
|
||||||
@@ -108,7 +106,7 @@ 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.
|
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/)**
|
2. **[Peter Bucher](http://www.aspnetzone.de/blogs/peterbucher/)**
|
||||||
|
|
||||||
[August 11, 2009 um 7:45 pm](/posts/cleancode1/)
|
[August 11, 2009 um 7:45 pm](/posts/cleancode1/)
|
||||||
@@ -144,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.
|
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.
|
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.
|
Grüße aus dem Frankenland.
|
||||||
|
|
||||||
|
|||||||
@@ -105,9 +105,7 @@ In diesem Kapitel zeigt Uncle Bob eine Liste von Smells und wie man sie beseitig
|
|||||||
|
|
||||||
**Zusammenfassung**
|
**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.
|
||||||
|
|
||||||
<img src="/images/cleancode2/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,11 +129,9 @@ 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" ) );
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Ich hoffe, das Beispiel ist ausführlich genug, um die Vorteile von DbC zu highlighten. [Stefan](http://www.lieser-online.de/blog/), vielen dank noch mal für den Artikel, ich habe mich natürlich von dir inspirieren lassen.
|
Ich hoffe, das Beispiel ist ausführlich genug, um die Vorteile von DbC zu highlighten. [Stefan](http://www.lieser-online.de/blog/), vielen dank noch mal für den Artikel, ich habe mich natürlich von dir inspirieren lassen.
|
||||||
|
|
||||||
<img src="/images/design-by-contract-teil2/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ Wikipedia definiert DbC folgendermaßen:
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
<img src="/images/design-by-contract/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **[Rainer Hilmer](http://dotnet-forum.de/blogs/rainerhilmer/default.aspx)**
|
1. **[Rainer Hilmer](http://dotnet-forum.de/blogs/rainerhilmer/default.aspx)**
|
||||||
@@ -39,7 +37,7 @@ Das soll nicht bedeuten, dass der Entwickler der Methode die Verantwortung von s
|
|||||||
|
|
||||||
Hallo Rainer,
|
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.
|
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.
|
Mir ging es eigentlich darum, dass es endlich möglich ist, Anforderungen als Code zu implementieren und als solche schriftlich festhalten, verfolgen und auswerten.
|
||||||
|
|||||||
@@ -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.
|
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)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +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.
|
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!
|
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"/>
|
<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))
|
(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:
|
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/))
|
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/))
|
||||||
@@ -33,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/).
|
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.
|
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ Mit dieser Beispielanwendung kann man eine mögliche Implementierung von Model V
|
|||||||
Anhang:
|
Anhang:
|
||||||
[Kompletter Sourcecode der Beispielanwendung als ZIP](/assets/TwitterClient.zip)
|
[Kompletter Sourcecode der Beispielanwendung als ZIP](/assets/TwitterClient.zip)
|
||||||
|
|
||||||
<img src="/images/mvp-winforms/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **[Jan Selke](http://jcselke.blogspot.com/)**
|
1. **[Jan Selke](http://jcselke.blogspot.com/)**
|
||||||
@@ -81,7 +79,7 @@ Anhang:
|
|||||||
|
|
||||||
Zu dem versehentlichen Verlassen:
|
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)…
|
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,
|
Viele Grüße,
|
||||||
Jan
|
Jan
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ Ich musste allerdings beim Lesen ständig auf die Situation “danach” denken,
|
|||||||
|
|
||||||
Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? Würde das die unpassenden Personen ausfiltern? Oder soll man sich die Zeit nehmen und dem Kandidaten “ungesehen” die Probezeit anbieten? Würde das vielleicht zu viele Personen ausfiltern? Kann man sich das überhaupt erlauben in der heutigen Zeit der Fachkräftemangel? (Gut, man fragt sich natürlich, ob wir hier über “Fachkraft” sprechen dürfen…)
|
Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? Würde das die unpassenden Personen ausfiltern? Oder soll man sich die Zeit nehmen und dem Kandidaten “ungesehen” die Probezeit anbieten? Würde das vielleicht zu viele Personen ausfiltern? Kann man sich das überhaupt erlauben in der heutigen Zeit der Fachkräftemangel? (Gut, man fragt sich natürlich, ob wir hier über “Fachkraft” sprechen dürfen…)
|
||||||
|
|
||||||
<img src="/images/programmierer/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. <img src="/images/programmierer/3e7b6cb163ea9cc8549abcb8b5a03e2e.jpeg" width="32" height="32" class="avatar avatar-32 photo" alt="Avatar"/> **[Ralf Westphal](http://ralfw.blogspot.com/)**
|
1. <img src="/images/programmierer/3e7b6cb163ea9cc8549abcb8b5a03e2e.jpeg" width="32" height="32" class="avatar avatar-32 photo" alt="Avatar"/> **[Ralf Westphal](http://ralfw.blogspot.com/)**
|
||||||
@@ -43,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.
|
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.
|
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**
|
3. **Christina Hirth**
|
||||||
@@ -116,7 +114,7 @@ Was haltet ihr von Coding bei der Einstellung? Ist das auch in Europa üblich? W
|
|||||||
|
|
||||||
Hallo André,
|
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?
|
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?
|
||||||
|
|
||||||
@@ -128,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/)
|
[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.
|
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/)**
|
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/)**
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +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 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"/>
|
||||||
|
|
||||||
<img src="/images/refaktorisierung/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|||||||
+12
-22
@@ -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?
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ Wir sind nun in Sprint 21 und nach 2 Open Spaces um ausreichenden Erfahrungsaust
|
|||||||
|
|
||||||
Ich rate allen, die SCRUM einführen wollen, sich wirklich so nah wie möglich an SCRUM zu halten. Die Sprints 1 – 15 wirkten bei uns auch sehr gut, doch erst danach fiel uns auf, dass gerade die Sprint-Retrospektive ein sehr wichtiges Meeting ist. All diese Regeln sind durch mehrjährige Erfahrungen entstanden und sie haben sich schon in größeren Firmen bewährt. Warum dann nicht davon profitieren?
|
Ich rate allen, die SCRUM einführen wollen, sich wirklich so nah wie möglich an SCRUM zu halten. Die Sprints 1 – 15 wirkten bei uns auch sehr gut, doch erst danach fiel uns auf, dass gerade die Sprint-Retrospektive ein sehr wichtiges Meeting ist. All diese Regeln sind durch mehrjährige Erfahrungen entstanden und sie haben sich schon in größeren Firmen bewährt. Warum dann nicht davon profitieren?
|
||||||
|
|
||||||
<img src="/images/scrumbut/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **Mike Bild**
|
1. **Mike Bild**
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ Wir haben noch einen langen und interessanten Weg vor uns, aber eins ist jetzt s
|
|||||||
|
|
||||||
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"/>
|
||||||
|
|
||||||
<img src="/images/softwarezellen/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **[Tweets that mention build broken » Softwarezellen – eine Lösung um die Komplexität zu beherrschen -- Topsy.com](http://topsy.com/tb/bit.ly/aYQGup)**
|
1. **[Tweets that mention build broken » Softwarezellen – eine Lösung um die Komplexität zu beherrschen -- Topsy.com](http://topsy.com/tb/bit.ly/aYQGup)**
|
||||||
|
|||||||
@@ -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 “ />
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ InternalsVisibleTo("ThirdAssembly")]`
|
|||||||
|
|
||||||
The former syntax is legal but fails, because each instance simply redefines and replaces any earlier ones, the latter syntax works as required.
|
The former syntax is legal but fails, because each instance simply redefines and replaces any earlier ones, the latter syntax works as required.
|
||||||
|
|
||||||
<img src="/images/test-public-methods/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. **Thomas Christain**
|
1. **Thomas Christain**
|
||||||
|
|||||||
@@ -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() );
|
||||||
}
|
}
|
||||||
@@ -190,4 +173,3 @@ Dieses Vorgehen hat uns nicht nur den seit langen gesuchten Weg zum Testen von W
|
|||||||
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)
|
[Download VS2008-Projekt](/assets/TestableWebForm.zip)
|
||||||
|
|
||||||
<img src="/images/unittests-webforms/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ Um uns möglichst nah an den Projekten in der Arbeit zu orientieren, haben wir e
|
|||||||
|
|
||||||
Glückwunsch nochmal an die Gewinner des Wettbewerbs (Schiffe versenken).
|
Glückwunsch nochmal an die Gewinner des Wettbewerbs (Schiffe versenken).
|
||||||
|
|
||||||
<img src="/images/webcamps/KickItImageGenerator.ashx" alt="kick it on dotnet-kicks.de"/>
|
|
||||||
|
|
||||||
## Kommentare (Archiv)
|
## Kommentare (Archiv)
|
||||||
|
|
||||||
1. <img src="/images/webcamps/3e7b6cb163ea9cc8549abcb8b5a03e2e.jpeg" width="32" height="32" class="avatar avatar-32 photo" alt="Avatar"/> **[Ralf Westphal](http://ralfw.blogspot.com/)**
|
1. <img src="/images/webcamps/3e7b6cb163ea9cc8549abcb8b5a03e2e.jpeg" width="32" height="32" class="avatar avatar-32 photo" alt="Avatar"/> **[Ralf Westphal](http://ralfw.blogspot.com/)**
|
||||||
@@ -52,7 +50,7 @@ Glückwunsch nochmal an die Gewinner des Wettbewerbs (Schiffe versenken).
|
|||||||
|
|
||||||
Hallo Ralph,
|
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.
|
Wir wollten es eben so machen wie an der Arbeit.
|
||||||
|
|
||||||
Gruß
|
Gruß
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ Noch ein paar Bemerkungen:
|
|||||||
|
|
||||||
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!
|
Falls es nichts wird, melde ich mich noch einmal!
|
||||||
4. **kai**
|
4. **kai**
|
||||||
@@ -127,8 +127,8 @@ Noch ein paar Bemerkungen:
|
|||||||
|
|
||||||
strRet = objRequest.responseText steht in dieser Zeile.
|
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
|
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.
|
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.
|
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/)
|
[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**
|
8. **Christina Hirth**
|
||||||
|
|
||||||
[Dezember 15, 2009 um 1:03 pm](/posts/webservice-mit-asp-classic/)
|
[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.
|
Du musst diesen Webservice Publishen, ihn auf einen erreichbaren Webserver veröffentlichen. Dann müsste alles passen.
|
||||||
9. **kai**
|
9. **kai**
|
||||||
@@ -165,7 +165,7 @@ Noch ein paar Bemerkungen:
|
|||||||
|
|
||||||
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!
|
Eine Frage bleibt bei mir aber noch offen!
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ 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?
|
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**
|
10. **Christina Hirth**
|
||||||
|
|
||||||
[Dezember 15, 2009 um 3:20 pm](/posts/webservice-mit-asp-classic/)
|
[Dezember 15, 2009 um 3:20 pm](/posts/webservice-mit-asp-classic/)
|
||||||
@@ -196,7 +196,7 @@ Noch ein paar Bemerkungen:
|
|||||||
|
|
||||||
Deine Default-Theorie kann es leider nicht sein, weil ich bereits 2 Methoden in meinem Webservice habe.
|
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"/>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: "Schlagwörter"
|
||||||
|
---
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Addin"
|
||||||
|
---
|
||||||
|
|
||||||
|
Das Visual Studio Add-in „Copy Source As HTML" — für formatierte Code-Übertragung in HTML, ideal für Blogs und Dokumentation.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Architektur"
|
||||||
|
---
|
||||||
|
|
||||||
|
Softwarezellen, Design by Contract und Plug-In-Architekturen — Gedanken zu Struktur, Verträgen und klarem Aufbau von Software.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "ASP-Classic"
|
||||||
|
---
|
||||||
|
|
||||||
|
SOAP-Kommunikation zwischen ASP-Classic und .NET-Webservices — technische Anleitungen für den Legacy-Betrieb.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Assemblies"
|
||||||
|
---
|
||||||
|
|
||||||
|
Grundlagen zu .NET-Assemblies: Aufbau, Laden und Reflexion für dynamische Architekturen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Assembly"
|
||||||
|
---
|
||||||
|
|
||||||
|
Dynamisches Laden von Assemblies und Reflection — Schlüsseltechnologien für flexible Plug-In-Systeme.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Basics"
|
||||||
|
---
|
||||||
|
|
||||||
|
Die Grundlagen der professionellen Software-Entwicklung — von sauberer Codebasis bis zu bewährten Coding-Practices.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Buch"
|
||||||
|
---
|
||||||
|
|
||||||
|
Buchbesprechungen und Zusammenfassungen — von „Clean Code" über „Code Complete" bis zu „The Pragmatic Programmer".
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Buchempfehlung"
|
||||||
|
---
|
||||||
|
|
||||||
|
Unsere Empfehlungen für pflichtlektürenwertige Bücher zur software-Entwicklung.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "C#"
|
||||||
|
---
|
||||||
|
|
||||||
|
Praktische C#-Entwicklung: Anonyme Datentypen, dynamisches Assembly-Laden, Plug-In-Patterns und Design by Contract mit Spec#.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "CCD"
|
||||||
|
---
|
||||||
|
|
||||||
|
Der Clean Code Developer — von der Yellow Brick Road bis zu SOLID, DRY und professioneller Code-Kultur.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Clean Code"
|
||||||
|
---
|
||||||
|
|
||||||
|
Sauberer, lesbarer und wartbarer Code — unsere Erfahrungen mit Clean Code, Refactoring und bewährten Development-Practices.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Coding"
|
||||||
|
---
|
||||||
|
|
||||||
|
Buch-Zusammenfassungen und Praxis-Tipps für besseres Programmieren — direkt aus dem Alltag als .NET-Entwickler.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Community"
|
||||||
|
---
|
||||||
|
|
||||||
|
Open Spaces, BarCamps und User Groups — unser Austausch mit der .NET-Entwickler-Community.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Contract-First"
|
||||||
|
---
|
||||||
|
|
||||||
|
Beim MVP-Pattern mit WinForms: Verträge zuerst definieren, dann implementieren — für testbare und klare Architekturen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Datenbank"
|
||||||
|
---
|
||||||
|
|
||||||
|
Von relationalen Datenbanken zu NoSQL — StupidDB als schemaloser Objektspeicher für .NET.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Datenschutz"
|
||||||
|
---
|
||||||
|
|
||||||
|
Datenschutz im Social Web — Diskussionen und Erfahrungen von BarCamps und in der Community.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Facebook"
|
||||||
|
---
|
||||||
|
|
||||||
|
Eindrücke und Diskussionen rund um soziale Plattformen wie Facebook — vom BarCamp bis zur Datenschutzdebatte.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "HowTo"
|
||||||
|
---
|
||||||
|
|
||||||
|
Praktische Schritt-für-Schritt-Anleitungen: Reflection, dynamisches Assembly-Laden, anonyme Datentypen und Plug-In-Architekturen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "HTML"
|
||||||
|
---
|
||||||
|
|
||||||
|
HTML-Techniken in der Praxis: Code-Formatierung für Webseiten und die Umwandlung von Visual-Studio-Quellcode.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "MVC"
|
||||||
|
---
|
||||||
|
|
||||||
|
ASP.NET MVC und Model-View-Presenter — Erfahrungsberichte, Vorträge und Implementierungsbeispiele.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "MVP"
|
||||||
|
---
|
||||||
|
|
||||||
|
Das Model-View-Presenter-Pattern in der Praxis — mit Contract-First-Ansatz in WinForms.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "NoSQL"
|
||||||
|
---
|
||||||
|
|
||||||
|
NoSQL mit StupidDB — ein schemaloser Objektspeicher für .NET, einfach und flexibel.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Open Space"
|
||||||
|
---
|
||||||
|
|
||||||
|
Eindrücke von .NET Open Spaces in Leipzig und Ulm — intensiver Austausch ohne Agenda, direkt mit den besten Köpfen der Branche.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Patterns"
|
||||||
|
---
|
||||||
|
|
||||||
|
Entwurfsmuster und WebCast-Empfehlungen — von async-Patterns bis zu bewährten Solution-Patterns.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Plug-In"
|
||||||
|
---
|
||||||
|
|
||||||
|
Plug-In-Architekturen mit Reflection und dynamischem Assembly-Laden — für erweiterbare .NET-Anwendungen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "prio.conference"
|
||||||
|
---
|
||||||
|
|
||||||
|
Tagungsbericht von der prio.conference 2009 in München — Sessions zu UI-Entwicklung, Usability und agiler Softwareentwicklung.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Refaktorisierung"
|
||||||
|
---
|
||||||
|
|
||||||
|
Refactoring mal anders erklärt — mit Metaphern aus dem Alltag und konkreten Beispielen aus der Praxis.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Reflection"
|
||||||
|
---
|
||||||
|
|
||||||
|
.NET-Reflection: Wozu dient sie, wie funktioniert dynamisches Assembly-Laden, und warum ist sie der Schlüssel zu flexiblen Architekturen?
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Schemalos"
|
||||||
|
---
|
||||||
|
|
||||||
|
Kein starres Schema, volle Flexibilität — StupidDB als NoSQL-Ansatz für die einfache Objektpersistierung.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "SCRUM"
|
||||||
|
---
|
||||||
|
|
||||||
|
Reflexion über unsere SCRUM-Einführung — was lief gut, was lief anders, und warum „Scrumbut" kein Schimpfwort ist.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Social Web"
|
||||||
|
---
|
||||||
|
|
||||||
|
Dezentrale soziale Protokolle, Facebook und Datenschutz — Diskussionen vom BarCamp Bodensee.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Sonne"
|
||||||
|
---
|
||||||
|
|
||||||
|
Sommer, Sonne und BarCamp — Eindrücke von Events und Community-Treffen im Freien.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Sourcecode"
|
||||||
|
---
|
||||||
|
|
||||||
|
Code-Formatierung und -Übertragung: Wie man Quellcode aus Visual Studio sauber in HTML und Blog-Artikel bekommt.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "StupidDB"
|
||||||
|
---
|
||||||
|
|
||||||
|
Unser Open-Source-Framework zur einfachen Objektpersistierung auf dem Dateisystem — vom ersten Entwurf bis zu NoSQL-Konzepten.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Unit Tests"
|
||||||
|
---
|
||||||
|
|
||||||
|
Unit-Tests in der Praxis: Strategien für WebForms, nicht-öffentliche Methoden und Design by Contract.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Veranstaltung"
|
||||||
|
---
|
||||||
|
|
||||||
|
Open Spaces, BarCamps, User Groups und WebCamps — Berichte von .NET-Veranstaltungen und Community-Treffen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Visual Studio"
|
||||||
|
---
|
||||||
|
|
||||||
|
Nützliche Tools und Add-ins für Visual Studio — wie „Copy Source As HTML" für bessere Code-Dokumentation.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Webapplication"
|
||||||
|
---
|
||||||
|
|
||||||
|
Architektur und Testbarkeit von Webanwendungen — von Schichtenarchitektur bis zu Workarounds für bessere Unit-Tests.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "WebCamp"
|
||||||
|
---
|
||||||
|
|
||||||
|
Bericht vom Microsoft WebCamp in München — zwei Tage zu MVC2, Entity Framework und jQuery.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "WebCast"
|
||||||
|
---
|
||||||
|
|
||||||
|
Empfehlenswerte WebCasts und Podcasts für Entwickler — von Dime Casts bis zu Hanselminutes.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Webservice"
|
||||||
|
---
|
||||||
|
|
||||||
|
SOAP-Kommunikation und Webservice-Integration — Technische Anleitungen für den Austausch zwischen .NET und anderen Systemen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "WinForms"
|
||||||
|
---
|
||||||
|
|
||||||
|
Windows Forms in der Praxis — vom MVP-Pattern bis zu testbaren Desktop-Anwendungen.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "Wordpress"
|
||||||
|
---
|
||||||
|
|
||||||
|
WordPress als Blog-Plattform — Erfahrungen mit Code-Export, Theme-Anpassungen und Content-Migration.
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
{{- $desc := "" }}
|
{{- $desc := "" }}
|
||||||
{{- if .IsHome }}
|
{{- if .IsHome }}
|
||||||
{{- $desc = .Site.Params.description }}
|
{{- $desc = .Site.Params.description }}
|
||||||
|
{{- else if .Description }}
|
||||||
|
{{- $desc = .Description }}
|
||||||
|
{{- else if .Content }}
|
||||||
|
{{- $desc = .Content | plainify }}
|
||||||
{{- else if .IsNode }}
|
{{- else if .IsNode }}
|
||||||
{{- if eq .Kind "term" }}
|
{{- if eq .Kind "term" }}
|
||||||
{{- if eq .Data.Singular "tag" }}
|
{{- if eq .Data.Singular "tag" }}
|
||||||
@@ -55,8 +59,11 @@
|
|||||||
{{- if not $desc }}{{ $desc = .Summary | plainify }}{{ end }}
|
{{- if not $desc }}{{ $desc = .Summary | plainify }}{{ end }}
|
||||||
{{- $desc = strings.TrimSpace (strings.TrimRight "…" $desc) | truncate 160 }}
|
{{- $desc = strings.TrimSpace (strings.TrimRight "…" $desc) | truncate 160 }}
|
||||||
{{- if $desc }}<meta name="description" content="{{ $desc }}">{{ end }}
|
{{- if $desc }}<meta name="description" content="{{ $desc }}">{{ end }}
|
||||||
<link rel="canonical" href="{{ .Permalink }}">
|
{{- if eq .Kind "404" }}<meta name="robots" content="noindex">{{ end }}
|
||||||
<meta property="og:type" content="{{ if .IsHome }}website{{ else }}article{{ 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:site_name" content="{{ .Site.Title }}">
|
||||||
<meta property="og:title" content="{{ $title }}">
|
<meta property="og:title" content="{{ $title }}">
|
||||||
<meta property="og:url" content="{{ .Permalink }}">
|
<meta property="og:url" content="{{ .Permalink }}">
|
||||||
@@ -64,7 +71,7 @@
|
|||||||
{{- if .Params.images }}
|
{{- if .Params.images }}
|
||||||
{{- range .Params.images }}<meta property="og:image" content="{{ . | absURL }}">{{ end }}
|
{{- range .Params.images }}<meta property="og:image" content="{{ . | absURL }}">{{ end }}
|
||||||
{{- else }}<meta property="og:image" content="{{ .Site.Params.bannerImage | 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:card" content="summary_large_image">
|
||||||
<meta name="twitter:title" content="{{ $title }}">
|
<meta name="twitter:title" content="{{ $title }}">
|
||||||
{{- if $desc }}<meta name="twitter:description" content="{{ $desc }}">{{ end }}
|
{{- if $desc }}<meta name="twitter:description" content="{{ $desc }}">{{ end }}
|
||||||
@@ -93,7 +100,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<p>© 2009-{{ now.Format "2006" }} build broken · <a href="{{ "/impressum/" | relURL }}">Impressum</a> · archiviert als statische Seite · part of the <a href="https://hub.moonweb.org">moonweb.org</a> homelab.</p>
|
<p>© 2009-{{ now.Format "2006" }} build broken · <a href="{{ "/impressum/" | relURL }}">Impressum</a> · archiviert als statische Seite · part of the <a href="https://www.moonweb.org">moonweb.org</a> homelab.</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<dialog id="broken-link-dialog" class="broken-link-dialog">
|
<dialog id="broken-link-dialog" class="broken-link-dialog">
|
||||||
@@ -107,7 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</dialog>
|
</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>
|
<script src="{{ "js/link-status.js" | relURL }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -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,4 +1,5 @@
|
|||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
|
{{ with .Content }}<div class="taxonomy-intro">{{ . }}</div>{{ end }}
|
||||||
<h2 class="archive-title">{{ .Title }}</h2>
|
<h2 class="archive-title">{{ .Title }}</h2>
|
||||||
<div class="post-list">
|
<div class="post-list">
|
||||||
{{ range .Pages }}
|
{{ range .Pages }}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
{{ $paginator := .Paginate (where .Site.RegularPages "Type" "posts") }}
|
{{ $paginator := .Paginate (where .Site.RegularPages "Type" "posts") }}
|
||||||
{{ if eq $paginator.PageNumber 1 }}
|
{{ 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 }}
|
{{ with .Content }}
|
||||||
<div class="about-intro">{{ . }}</div>
|
<div class="about-intro">{{ . }}</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user