Compare commits

..
16 Commits
Author SHA1 Message Date
stefankoelle d36d13b44e seo fixes 2026-09-11 18:18:18 +02:00
stefankoelle 75eb7cc749 hub subdomain is now www 2026-09-11 14:33:19 +02:00
stefankoelle 2e307dcc86 many fixes 2026-09-10 23:34:39 +02:00
stefankoelle cd8a75ec13 fix code blocks 2026-09-10 22:54:41 +02:00
stefankoelle 2dc30200a1 removed all old dotnet kicks badges 2026-08-22 15:52:54 +02:00
stefankoelle 92248b5ad7 fix markdown 2026-08-22 15:44:49 +02:00
stefankoelle 646b30d91d TODO.md 2026-08-22 12:57:02 +02:00
stefankoelle abc6c54cf6 fix category/tags meta description 2026-08-22 12:55:20 +02:00
stefankoelle 6b1dfd43d3 fix alt texts 2026-08-22 12:52:16 +02:00
stefankoelle b6345be087 README.md 2026-08-22 12:16:25 +02:00
stefankoelle 83282a00a1 README.md 2026-08-22 12:13:05 +02:00
stefankoelle df465d34df AGENTS.md 2026-08-22 11:55:30 +02:00
stefankoelle 15fdec825a README.md 2026-08-22 11:49:28 +02:00
stefankoelle 4c697f1928 tags pages with description 2026-08-22 10:40:16 +02:00
stefankoelle 2452b7e108 title normalized 2026-08-22 10:12:20 +02:00
stefankoelle 9bf9059f39 category pages with description 2026-08-22 10:07:24 +02:00
96 changed files with 1087 additions and 815 deletions
+2 -1
View File
@@ -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.
+147 -68
View File
@@ -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, 20092010) > ursprünglich von den Autoren von "build broken" (aztec-project.org, 20092010)
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
### 1⃣ HTML herunterladen
Alle Dateien aus `URLS.md` als HTML von web.archive.org herunterladen und
unter `archive/wayback-html/<dateiname>.html` ablegen (exakt gleicher Name). unter `archive/wayback-html/<dateiname>.html` ablegen (exakt gleicher Name).
2. Parser laufen lassen:
### 2⃣ Parser laufen lassen
```bash ```bash
cd scripts cd scripts
pip install -r requirements.txt pip install -r requirements.txt
python parse_buildbroken_archive.py --source ../archive/wayback-html --output .. --download-images python parse_buildbroken_archive.py --source ../archive/wayback-html --output .. --download-images
``` ```
> **Wichtig:** Der Parser soll **nicht mehr erneut ausgeführt** werden. Die
> ⚠️ **Wichtig:** Der Parser soll **nicht mehr erneut ausgeführt** werden. Die
> generierten Artikel in `content/posts/*.md` wurden nachträglich manuell > generierten Artikel in `content/posts/*.md` wurden nachträglich manuell
> ausgebessert (Titel gekürzt, Tags korrigiert, `alt`-Attribute ergänzt, > ausgebessert (Titel gekürzt, Tags korrigiert, `alt`-Attribute ergänzt,
> Links umgeschrieben). Ein erneuter Lauf würde diese Handarbeit überschreiben. > Links umgeschrieben. Ein erneuter Lauf würde diese Handarbeit überschreiben.
3. Lokale Vorschau:
### 3⃣ Lokale Vorschau
```bash ```bash
cd .. cd ..
hugo server -D --bind 0.0.0.0 --baseURL http://<hostname>:1313/ hugo server -D --bind 0.0.0.0 --baseURL http://<hostname>:1313/
``` ```
`<hostname>` durch den Hostnamen des Rechners ersetzen (z. B. `xubuntu-dev.lan`). `<hostname>` durch den Hostnamen des Rechners ersetzen (z. B. `xubuntu-dev.lan`).
4. Produktions-Build:
### 4⃣ Produktions-Build
```bash ```bash
hugo --minify hugo --minify
``` ```
Ergebnis liegt in `public/` und kann auf beliebigem statischen Hosting
🌍 Ergebnis liegt in `public/` und kann auf beliebigem statischen Hosting
(nginx-Container, Gitea Pages, GitHub Pages) deployt werden. (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 ## 🔍 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
+21
View File
@@ -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*
+3
View File
@@ -0,0 +1,3 @@
---
title: "Autoren"
---
+3
View File
@@ -0,0 +1,3 @@
---
title: "Kategorien"
---
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
title: "MVC"
---
Erfahrungsberichte und Implementierungsbeispiele zu ASP.NET MVC und dem Model-View-Presenter-Pattern in WinForms.
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Release"
---
Vorstellung unseres Open-Source-Frameworks StupidDB zur einfachen Objektpersistierung auf dem Dateisystem.
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Webservice"
---
Technische Anleitungen zur SOAP-Kommunikation — wie der sichere Austausch zwischen ASP-Classic und .NET-Webservices funktioniert.
+2 -1
View File
@@ -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/)
+3
View File
@@ -0,0 +1,3 @@
---
title: "Beiträge"
---
+1 -3
View File
@@ -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/)
+3 -3
View File
@@ -13,7 +13,7 @@ Anonyme Datentypen sind Klassen, die erst beim Kompilieren durch den Compiler de
Definiert und intstanziert werden sie mit new und einem Objektinitialisierer: Definiert und intstanziert werden sie mit new und einem Objektinitialisierer:
``` ```csharp
var mitarbeiter = new { Name = "Müller", Abteilung = "IT" }; var mitarbeiter = new { Name = "Müller", Abteilung = "IT" };
``` ```
@@ -21,7 +21,7 @@ var ist der implizite Typ und **nicht** der anonyme Datentyp. Er fungiert hier l
Gibt man keinen Namen für die Properties an, werden die Namen der Properties verwendet, mit denen initialisiert wird. Gibt man keinen Namen für die Properties an, werden die Namen der Properties verwendet, mit denen initialisiert wird.
``` ```csharp
var abteilung = new { Abteilungsname = "IT" }; var abteilung = new { Abteilungsname = "IT" };
var mitarbeiter = new { Name = "Müller" , abteilung.Abteilungsname }; var mitarbeiter = new { Name = "Müller" , abteilung.Abteilungsname };
Console .WriteLine(mitarbeiter.Abteilungsname); Console .WriteLine(mitarbeiter.Abteilungsname);
@@ -33,7 +33,7 @@ Anonyme Datentypen bieten sich an, wenn man Abfragen mit Linq ausführt und entw
Hier ein Beispiel aus einem Castle Monorail Projekt, in dem eine erweiterte Liste an eine vm gegeben wird, ohne eine neue Klasse definieren zu müssen. Es soll dabei eine Liste erstellt werden, in der die Mitarbeiter mit ihrem Horoskop verknüpft werden : Hier ein Beispiel aus einem Castle Monorail Projekt, in dem eine erweiterte Liste an eine vm gegeben wird, ohne eine neue Klasse definieren zu müssen. Es soll dabei eine Liste erstellt werden, in der die Mitarbeiter mit ihrem Horoskop verknüpft werden :
``` ```csharp
public void Action() { public void Action() {
IList < Mitarbeiter > mitarbeiterListe = m_service.GetMitarbeiter(); IList < Mitarbeiter > mitarbeiterListe = m_service.GetMitarbeiter();
var mitarbeiterMitHoroskopListe = ( from mitarbeiter in mitarbeiterListe var mitarbeiterMitHoroskopListe = ( from mitarbeiter in mitarbeiterListe
+42 -85
View File
@@ -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.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1(calcProxy)); 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) { public Form1(ICalcProxy calcProxy) {
InitializeComponent();
m_calcProxy = calcProxy;
m_calcProxy.CalcCompleted += CalculatorCalcCompleted;
}
        InitializeComponent(); private void Run_Click(object sender, EventArgs e) {
        m\_calcProxy = calcProxy;
        m\_calcProxy.CalcCompleted += CalculatorCalcCompleted;
    }
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_calcProxy.CalcAsync(number, number);
            m\_calcProxy.CalcAsync(number, number); }
}
        }
    }
void CalculatorCalcCompleted(object sender, CalcEventArgs eventArgs) { void CalculatorCalcCompleted(object sender, CalcEventArgs eventArgs) {
lblCounter.Text = eventArgs.UserState.ToString();
        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;
private readonly ICalculator m\_calculator;
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 CalcProxy(ICalculator calculator) { public CalcProxy(ICalculator calculator) {
m_calculator = calculator;
        m\_calculator = calculator; }
    }
public int Calc(int number) { public int Calc(int number) {
return m_calculator.Calc(number);
return m\_calculator.Calc(number); }
    }
public void CalcAsync(int number, object userState) { public void CalcAsync(int number, object userState) {
lock (this) { lock (this) {
if (m_isRunning) {
if (m\_isRunning) {
throw new InvalidOperationException("Diese Operation wird bereits ausgeführt"); throw new InvalidOperationException("Diese Operation wird bereits ausgeführt");
}
            } m_isRunning = true;
m_asyncOperation = AsyncOperationManager.CreateOperation(userState);
            m\_isRunning = true;
            m\_asyncOperation = AsyncOperationManager.CreateOperation(userState);
ThreadPool.QueueUserWorkItem(ExecuteCalc, number); ThreadPool.QueueUserWorkItem(ExecuteCalc, number);
}
        } }
    }
private void ExecuteCalc(object state) { private void ExecuteCalc(object state) {
var result = Calc((int)state); var result = Calc((int)state);
m_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result);
        m\_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result); }
    }
private void CalcCompletedSuccessful(object result) { private void CalcCompletedSuccessful(object result) {
if (CalcCompleted != null) { if (CalcCompleted != null) {
CalcCompleted(this, new CalcEventArgs(null, false, (int)result, result));
            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); Thread.Sleep(10000);
return number * number;
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/)
+5 -66
View File
@@ -42,7 +42,7 @@ 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() {
@@ -68,7 +68,7 @@ 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);
@@ -96,31 +96,7 @@ m_asyncOperation = AsyncOperationManager .CreateOperation(userState);
ThreadPool.QueueUserWorkItem(ExecuteCalc, number); 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.
```
public class Calculator : ICalculator {
```
        .
        .
        .
```
private void ExecuteCalc(object state) { private void ExecuteCalc(object state) {
int result = Calc((int)state); int result = Calc((int)state);
m_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result); m_asyncOperation.PostOperationCompleted(CalcCompletedSuccessful, result);
@@ -132,6 +108,7 @@ CalcCompleted( this , new CalcEventArgs ( null , false , ( int )result, resu
} }
} }
} }
public class CalcEventArgs : AsyncCompletedEventArgs { public class CalcEventArgs : AsyncCompletedEventArgs {
public CalcEventArgs(Exception error, bool cancelled, int result, object userState) public CalcEventArgs(Exception error, bool cancelled, int result, object userState)
: base(error, cancelled, userState) { : base(error, cancelled, userState) {
@@ -141,48 +118,10 @@ public int Result { get ; private set ; }
} }
``` ```
Wie man im Interface des Calculator sieht, gibt es eine synchrone und eine asynchrone Methode für die Berechnung. Uns interessiert allerdings nur die asynchrone Methode. Das lock und die Prüfung auf m\_isRunning verhindern lediglich, dass die asynchrone Methode während ihrer Ausführung öfter aufgerufen wird. Das Wesentliche an dieser Methode ist der Aufruf von AsyncOperationManager.CreateOperation, denn dieser Aufruf stellt einen Synchronisationskontext bereit, der die Threads miteinander synchronisiert. Für die eigentliche Berechnung wird sich über den ThreadPool ein neuer Thread besorgt, der sich dann um die Abarbeitung der ExecuteCalc in einem eigenen Thread kümmert.
Sobald die Berechnung abgeschlossen ist, wird der Thread mit dem MainThread über den Aufruf von PostOperationCompleted synchronisiert und das CalcCompleted-Event ausgelöst und das Ergebnis in das Label geschrieben (s. o.). 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/)**
+2 -4
View File
@@ -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.
+1 -3
View File
@@ -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)
+1 -1
View File
@@ -21,7 +21,7 @@ Hierfür habe ich folgende Konfiguration verwendet:
Nach der Bestätigung der Konfiguration sieht das Ergebnis folgendermaßen aus : Nach der Bestätigung der Konfiguration sieht das Ergebnis folgendermaßen aus :
``` ```csharp
/// <summary> /// <summary>
/// Summary /// Summary
/// </summary> /// </summary>
+5 -17
View File
@@ -15,7 +15,7 @@ Erstens muss man die IDE anpassen: im März kommt .NET 4.0 raus und da wird Desi
Jetzt zum Code: Nehmen wir eine ganz einfache Klasse `Bill` deren Objekte mit einem `IRepository` gespeichert bzw. geladen werden. Jetzt zum Code: Nehmen wir eine ganz einfache Klasse `Bill` deren Objekte mit einem `IRepository` gespeichert bzw. geladen werden.
``` ```csharp
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
namespace ContractsPrototyp namespace ContractsPrototyp
{ {
@@ -25,8 +25,6 @@ public int Id { get ; set ; }
public string Number { get ; set ; } public string Number { get ; set ; }
public double Value { get ; set ; } public double Value { get ; set ; }
} }
10
11
public interface IRepository public interface IRepository
{ {
Bill GetBill( string number); Bill GetBill( string number);
@@ -36,7 +34,7 @@ void SaveBill( Bill bill);
Die Kontrakte kann man in den einzelnen Methoden oder für eine ganze Klasse schreiben (unter dem Attribut `ContractInvariantMethode`) aber ich finde am schönsten, dass man die auch auslagern kann: durch eine gegenseitige Markierung können Kontrakt-Klassen und Interfaces als “Paare” definiert werden: Die Kontrakte kann man in den einzelnen Methoden oder für eine ganze Klasse schreiben (unter dem Attribut `ContractInvariantMethode`) aber ich finde am schönsten, dass man die auch auslagern kann: durch eine gegenseitige Markierung können Kontrakt-Klassen und Interfaces als “Paare” definiert werden:
``` ```csharp
[ ContractClass ( typeof ( RepositoryContracts ))] [ ContractClass ( typeof ( RepositoryContracts ))]
public interface IRepository public interface IRepository
{ {
@@ -51,7 +49,6 @@ public Bill GetBill( string number)
Contract .Requires(! string .IsNullOrEmpty(number)); Contract .Requires(! string .IsNullOrEmpty(number));
return null ; return null ;
} }
25
public void SaveBill( Bill bill) public void SaveBill( Bill bill)
{ {
Contract .Ensures(bill.Id > 0); Contract .Ensures(bill.Id > 0);
@@ -63,7 +60,7 @@ Eine Vorbedingung wird mit `Contract.Requires` und eine Nachbedingung mit `Contr
Die eigentliche Implementierung der Klasse schaut dann so aus: Die eigentliche Implementierung der Klasse schaut dann so aus:
``` ```csharp
public class Repository : IRepository public class Repository : IRepository
{ {
public Bill GetBill( string nummer) public Bill GetBill( string nummer)
@@ -71,13 +68,11 @@ public Bill GetBill( string nummer)
//Würde das Objekt aus Datenhaltung laden //Würde das Objekt aus Datenhaltung laden
return new Bill (); return new Bill ();
} }
38
public void SaveBill( Bill bill) public void SaveBill( Bill bill)
{ {
//Würde das Objekt speichern und ihm eine Id zuweisen //Würde das Objekt speichern und ihm eine Id zuweisen
if (BillIsValid( bill )) bill.Id++; if (BillIsValid( bill )) bill.Id++;
} }
44
private static bool BillIsValid( Bill bill) private static bool BillIsValid( Bill bill)
{ {
return ! string .IsNullOrEmpty(bill.Nummer); return ! string .IsNullOrEmpty(bill.Nummer);
@@ -88,13 +83,12 @@ return ! string .IsNullOrEmpty(bill.Nummer);
Woher können wir wissen, dass das funktioniert? Es ist einfach, wir schreiben ein Paar Tests dazu! Woher können wir wissen, dass das funktioniert? Es ist einfach, wir schreiben ein Paar Tests dazu!
Bei Kontraktverletzung wird eine Exception geworfen. Um diese und dadurch die genaue Verletzung überprüfen zu können braucht man etwas Workaround: Bei Kontraktverletzung wird eine Exception geworfen. Um diese und dadurch die genaue Verletzung überprüfen zu können braucht man etwas Workaround:
``` ```csharp
[ TestFixture ] [ TestFixture ]
public class BillTests public class BillTests
{ {
private IRepository m_repository; private IRepository m_repository;
private string m_message; private string m_message;
60
[ SetUp ] [ SetUp ]
public void Setup() public void Setup()
{ {
@@ -110,11 +104,10 @@ m_message = e.Message;
Danach sind die Tests dann einfach: Danach sind die Tests dann einfach:
``` ```csharp
[ Test ] [ Test ]
public void Laden_mit_leerer_Nummer_verletzt_Kontrakt() public void Laden_mit_leerer_Nummer_verletzt_Kontrakt()
{ {
76
try try
{ {
m_repository.GetBill( null ); m_repository.GetBill( null );
@@ -123,14 +116,11 @@ catch
{ {
//Nichts //Nichts
} }
85
Assert .That( m_message, Is .EqualTo( "Precondition failed: !string.IsNullOrEmpty(number)" ) ); Assert .That( m_message, Is .EqualTo( "Precondition failed: !string.IsNullOrEmpty(number)" ) );
} }
88
[ Test ] [ Test ]
public void Speichern_Rechnung_ohne_Nummer_verletzt_Kontrakt() public void Speichern_Rechnung_ohne_Nummer_verletzt_Kontrakt()
{ {
92
try try
{ {
m_repository.SaveBill( new Bill {Value = 25} ); m_repository.SaveBill( new Bill {Value = 25} );
@@ -139,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"/>
+1 -3
View File
@@ -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)
+5 -4
View File
@@ -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.
+1 -3
View File
@@ -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
+3 -11
View File
@@ -19,18 +19,16 @@ Voraussetzungen für dieses Pattern sind:
Zu allererst brauchen wir eine gemeinsame Schnittstelle, die sowohl vom Plug-In als auch vom Host (die Applikation welche das Plug-In verwenden soll) benutzt werden soll. Über diese Schnittstelle kommuniziert der Host mit dem Plug-In. Zu allererst brauchen wir eine gemeinsame Schnittstelle, die sowohl vom Plug-In als auch vom Host (die Applikation welche das Plug-In verwenden soll) benutzt werden soll. Über diese Schnittstelle kommuniziert der Host mit dem Plug-In.
Es würde sich das Interface IPlugIn anbieten. In meinem Fall verwende ich allerdings kein Interface sondern eine abstrakte Klasse, da ich bereits Logik direkt in die Schnittstelle implementieren möchte. In meinem Fall heißt die abstrakte Klasse einfach nur Plugin. Im weiteren Verlauf werde ich zu meiner abstrakten Klasse Schnittstelle sagen. Es würde sich das Interface IPlugIn anbieten. In meinem Fall verwende ich allerdings kein Interface sondern eine abstrakte Klasse, da ich bereits Logik direkt in die Schnittstelle implementieren möchte. In meinem Fall heißt die abstrakte Klasse einfach nur Plugin. Im weiteren Verlauf werde ich zu meiner abstrakten Klasse Schnittstelle sagen.
``` ```csharp
public abstract class Plugin { public abstract class Plugin {
private IHost m_host; private IHost m_host;
public Plugin( string name) { public Plugin( string name) {
Name = name; Name = name;
} }
6
public string Name { get ; set ; } public string Name { get ; set ; }
public string Author { get ; set ; } public string Author { get ; set ; }
public string Version { get ; set ; } public string Version { get ; set ; }
public bool IsRegistered { get ; private set ; } public bool IsRegistered { get ; private set ; }
11
/// <summary> /// <summary>
/// Setzt oder gibt die Host-Application. /// Setzt oder gibt die Host-Application.
/// </summary> /// </summary>
@@ -63,7 +61,7 @@ Zum einen ist es in der Regel so, dass ein Plug-In für nur einen Host entwickel
Das Interface IHost sieht folgendermaßen aus: Das Interface IHost sieht folgendermaßen aus:
``` ```csharp
public interface IHost { public interface IHost {
bool Register(Plugin plugin); bool Register(Plugin plugin);
bool Unregister(Plugin plugin); bool Unregister(Plugin plugin);
@@ -74,14 +72,12 @@ Dieses Interface muss von der Host-Applikation implementiert werden, damit sich
Eine vereinfachte Darstellung der Implementierung des IHost -Interfaces sieht folgendermaßen aus: Eine vereinfachte Darstellung der Implementierung des IHost -Interfaces sieht folgendermaßen aus:
``` ```csharp
public class HostApplication : IHost { public class HostApplication : IHost {
private List<Plugin> m_pluginList; private List<Plugin> m_pluginList;
3
//Konstruktor //Konstruktor
public HostApplication() { public HostApplication() {
m_pluginList = new List<Plugin>(); m_pluginList = new List<Plugin>();
7
//Lädt alle verfügbaren Plug-Ins zb. aus einem Verzeichnis //Lädt alle verfügbaren Plug-Ins zb. aus einem Verzeichnis
List<Plugin> plugins = GetPlugins(); List<Plugin> plugins = GetPlugins();
foreach (Plugin plugin in plugins) { foreach (Plugin plugin in plugins) {
@@ -89,21 +85,18 @@ foreach (Plugin plugin in plugins) {
plugin.Host = this ; plugin.Host = this ;
} }
} }
15
public void ShowPlugins() { public void ShowPlugins() {
foreach (Plugin plugin in m_pluginList) { foreach (Plugin plugin in m_pluginList) {
//Ausgabe der Namen aller am Host registrierten Plug-Ins //Ausgabe der Namen aller am Host registrierten Plug-Ins
Console.WriteLine(plugin.Name); Console.WriteLine(plugin.Name);
} }
} }
22
public void UnloadPlugins() { public void UnloadPlugins() {
foreach (Plugin plugin in m_pluginList) { foreach (Plugin plugin in m_pluginList) {
//ruft implizit die IHost.Unregister-Methode auf //ruft implizit die IHost.Unregister-Methode auf
plugin.Host = null ; plugin.Host = null ;
} }
} }
29
#region IHost-Implementierung #region IHost-Implementierung
public bool Register(Plugin plugin) { public bool Register(Plugin plugin) {
if (!m_pluginList.Contains(plugin)) { if (!m_pluginList.Contains(plugin)) {
@@ -112,7 +105,6 @@ return true ;
} }
return false ; return false ;
} }
38
public bool Unregister(Plugin plugin) { public bool Unregister(Plugin plugin) {
if (m_pluginList.Contains(plugin)) { if (m_pluginList.Contains(plugin)) {
m_pluginList.Remove(plugin); m_pluginList.Remove(plugin);
+4 -6
View File
@@ -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/)**
+1 -2
View File
@@ -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
View File
@@ -19,7 +19,7 @@ Für dieses Beispiel habe ich eine kleine Konsolen-Anwendung geschrieben (TestAp
**TestClass.cs** **TestClass.cs**
``` ```csharp
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
namespace TestProject { namespace TestProject {
@@ -51,7 +51,7 @@ return value;
**SecondTestClass.cs** **SecondTestClass.cs**
``` ```csharp
namespace TestProject { namespace TestProject {
public class SecondTestClass { public class SecondTestClass {
public int Addition( int a, int b) { public int Addition( int a, int b) {
@@ -66,8 +66,6 @@ return a / b;
} }
} }
} }
.
.
``` ```
## Auslesen von Assembly- Type-Informationen ## Auslesen von Assembly- Type-Informationen
@@ -76,7 +74,7 @@ Mit der Methode Thread.GetDomain().GetAssemblies() bekommt man alle referenziert
Um sich nun alle Assemblies der aktuellen Domäne anzeigen zu lassen, könnte man folgendes schreiben: Um sich nun alle Assemblies der aktuellen Domäne anzeigen zu lassen, könnte man folgendes schreiben:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTest Class (); SecondTestClass secondTestClass = new SecondTest Class ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -88,11 +86,10 @@ Console .ReadLine();
} }
``` ```
.
Nun könnte man noch alle zur Verfügung stehenden Typen(Klassen) aus der TestProject-Assembly anzeigen. Nun könnte man noch alle zur Verfügung stehenden Typen(Klassen) aus der TestProject-Assembly anzeigen.
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass (); SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -110,11 +107,10 @@ Console .ReadLine();
} }
``` ```
.
Wenn man nun allerdings Informationen eines bestimmten Types haben möchte, so werden diese Informationen aus den Metadaten des Moduls geladen, in dem sich der Type befindet. Um zum Beispiel alle öffentlichen Methoden eines Types anzuzeigen, könnte man folgendes schreiben: Wenn man nun allerdings Informationen eines bestimmten Types haben möchte, so werden diese Informationen aus den Metadaten des Moduls geladen, in dem sich der Type befindet. Um zum Beispiel alle öffentlichen Methoden eines Types anzuzeigen, könnte man folgendes schreiben:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass (); SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -136,11 +132,10 @@ Console .ReadLine();
} }
``` ```
.
Es fällt auf, dass auch die Methoden ToString, Equals, GetHashCode und GetType in beiden Klassen angezeigt werden, obwohl diese in den Klassen überhaupt nicht deklariert wurden. Das liegt daran, dass alle Typen von object ableiten und object diese Methoden implementiert. Was noch auffällt ist, dass nur öffentliche Methoden angezeigt werden. Möchte man sich auch die privaten Methoden anzeigen lassen, so muss man der Methode type.GetMethods Parameter übergeben. Diese Methode verlangt BindingFlags, die man mit dem Bitweise ODER-Operator (|) miteinander verknüpfen kann. Um sich nun öffentliche und private Methoden anzeigen zu lassen, muss der Aufruf der Methode type.GetMethods folgendermaßen lauten: Es fällt auf, dass auch die Methoden ToString, Equals, GetHashCode und GetType in beiden Klassen angezeigt werden, obwohl diese in den Klassen überhaupt nicht deklariert wurden. Das liegt daran, dass alle Typen von object ableiten und object diese Methoden implementiert. Was noch auffällt ist, dass nur öffentliche Methoden angezeigt werden. Möchte man sich auch die privaten Methoden anzeigen lassen, so muss man der Methode type.GetMethods Parameter übergeben. Diese Methode verlangt BindingFlags, die man mit dem Bitweise ODER-Operator (|) miteinander verknüpfen kann. Um sich nun öffentliche und private Methoden anzeigen zu lassen, muss der Aufruf der Methode type.GetMethods folgendermaßen lauten:
``` ```csharp
MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
``` ```
@@ -148,7 +143,7 @@ Dieser Aufruf gibt an, dass man alle öffentlichen und nicht-öffentlichen Insta
Das ganze könnte man natürlich noch in der Form weiter treiben, dass man sich die kompletten Methodensignaturen mit Modifizierer, Schlüsselwort und Rückgabewert anzeigen lässt: Das ganze könnte man natürlich noch in der Form weiter treiben, dass man sich die kompletten Methodensignaturen mit Modifizierer, Schlüsselwort und Rückgabewert anzeigen lässt:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
SecondTestClass secondTestClass = new SecondTestClass (); SecondTestClass secondTestClass = new SecondTestClass ();
Assembly [] assemblies = Thread .GetDomain().GetAssemblies(); Assembly [] assemblies = Thread .GetDomain().GetAssemblies();
@@ -200,11 +195,10 @@ return parameters.ToString();
} }
``` ```
.
Nicht immer ist bereits während der Entwicklung bekannt, aus welchem Assembly man Informationen benötigt. Sodass die Methode Thread.GetDomain().GetAssemblies() nicht verwendet werden kann. Eine weitere Möglichkeit an ein Assembly zu kommen ist es direkt zu laden. Dafür bietet die Klasse Assembly die statische Methode Assembly.LoadFrom zum Laden von Assemblies. Statt dem Laden des Assembly über die aktuelle Domäne, wird das nächste Beispiel so modifiziert, dass die TestProject-Assembly direkt aus einem Verzeichnis heraus geladen wird. Nicht immer ist bereits während der Entwicklung bekannt, aus welchem Assembly man Informationen benötigt. Sodass die Methode Thread.GetDomain().GetAssemblies() nicht verwendet werden kann. Eine weitere Möglichkeit an ein Assembly zu kommen ist es direkt zu laden. Dafür bietet die Klasse Assembly die statische Methode Assembly.LoadFrom zum Laden von Assemblies. Statt dem Laden des Assembly über die aktuelle Domäne, wird das nächste Beispiel so modifiziert, dass die TestProject-Assembly direkt aus einem Verzeichnis heraus geladen wird.
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" ); Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) { if (assembly != null ) {
@@ -237,7 +231,7 @@ Man sieht, dass die gleiche Ausgabe wie bereits im vorherigen Beispiel generiert
Nachdem die Vorgehensweise nun klar sein sollte, möchte ich nun den eigentlich interessanten Teil demonstrieren. Wie kann man nun auf Methoden usw. zugreifen und wie können Werte aus einem Type gelesen und veränder werden? Wenn man die Methode SecondTestClass.Addition(int a, int b) aufrufen möchte, dann braucht man die Methodeninformationen von dem Type SecondTestClass aus dem TestProject-Assembly. D.h. man muss wie in den obigen Beispielen auch über alle Typen iterieren, um an den gesuchten Type SecondTestClass zu kommen. Hat man den gesuchten Type gefunden, so kann man sich von diesem alle Methoden in einem Array zurückgeben lassen. Dieses Array kann dann wieder ganz normal durchlaufen und nach der gewünschten Methode durchsucht werden. Hat man die Methode gefunden so muss zuerst eine Instanz von dem Objekt erzeugt werden und dafür gibt es den Activator, der die statische Methode CreateInstance beinhaltet. Als Parameter übergibt man ihr den Type von dem eine Instanz erzeugt werden soll. Jetzt kann man über die MethodInfo die Methode aufrufen. Dazu muss der Methode die Instanz des Objektes übergeben werden, von welchem die Methode aufgerufen werden soll und zusätzlich noch ein Array von Objekten, welche die zu übergebenen Parameter beinhaltet. Hat die Methode keine Parameter, so muss null übergeben werden. Dabei muss der Rückgabewert in den erwarteten Wert gecastet werden. Das ganze könnte dann folgendermaßen aussehen: Nachdem die Vorgehensweise nun klar sein sollte, möchte ich nun den eigentlich interessanten Teil demonstrieren. Wie kann man nun auf Methoden usw. zugreifen und wie können Werte aus einem Type gelesen und veränder werden? Wenn man die Methode SecondTestClass.Addition(int a, int b) aufrufen möchte, dann braucht man die Methodeninformationen von dem Type SecondTestClass aus dem TestProject-Assembly. D.h. man muss wie in den obigen Beispielen auch über alle Typen iterieren, um an den gesuchten Type SecondTestClass zu kommen. Hat man den gesuchten Type gefunden, so kann man sich von diesem alle Methoden in einem Array zurückgeben lassen. Dieses Array kann dann wieder ganz normal durchlaufen und nach der gewünschten Methode durchsucht werden. Hat man die Methode gefunden so muss zuerst eine Instanz von dem Objekt erzeugt werden und dafür gibt es den Activator, der die statische Methode CreateInstance beinhaltet. Als Parameter übergibt man ihr den Type von dem eine Instanz erzeugt werden soll. Jetzt kann man über die MethodInfo die Methode aufrufen. Dazu muss der Methode die Instanz des Objektes übergeben werden, von welchem die Methode aufgerufen werden soll und zusätzlich noch ein Array von Objekten, welche die zu übergebenen Parameter beinhaltet. Hat die Methode keine Parameter, so muss null übergeben werden. Dabei muss der Rückgabewert in den erwarteten Wert gecastet werden. Das ganze könnte dann folgendermaßen aussehen:
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" ); Assembly assembly = Assembly .LoadFrom( @"c:\TestProject.dll" );
if (assembly != null ) { if (assembly != null ) {
@@ -263,11 +257,10 @@ Console .ReadLine();
Aufruf der Methode Addition Aufruf der Methode Addition
.
Bei statischen Methoden kann das Erzeugen der Instanz weggelassen werden und für den Parameter des Instanz-Objektes kann null übergeben werden. Bei statischen Methoden kann das Erzeugen der Instanz weggelassen werden und für den Parameter des Instanz-Objektes kann null übergeben werden.
``` ```csharp
if (type.Name.Equals( "TestClass" )) { if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name); Console .WriteLine( " ++ {0}" , type.Name);
MethodInfo [] methodInfos = type.GetMethods( BindingFlags .NonPublic | BindingFlags .Static); MethodInfo [] methodInfos = type.GetMethods( BindingFlags .NonPublic | BindingFlags .Static);
@@ -282,11 +275,10 @@ Console .WriteLine( " ++ {0}(\"myPassword\") = {1}" , info.Name, value);
Das übergebene Passwort wurde über eine statische Methode gehashed. Das übergebene Passwort wurde über eine statische Methode gehashed.
.
Um den Wert einer Property auszulesen, muss anstelle von MethodInfo die PropertyInfo verwendet werden. Aus dieser kann dann über die Methode PropertyInfo.GetValue der aktuelle Wert zurückgegeben werden. Da in diesem Beispiel nach dem Instanzieren des Objektes noch keine Werte zugewiesen wurden, sind die Properties leer. Um den Wert einer Property auszulesen, muss anstelle von MethodInfo die PropertyInfo verwendet werden. Aus dieser kann dann über die Methode PropertyInfo.GetValue der aktuelle Wert zurückgegeben werden. Da in diesem Beispiel nach dem Instanzieren des Objektes noch keine Werte zugewiesen wurden, sind die Properties leer.
``` ```csharp
static void Main( string [] args) { static void Main( string [] args) {
Assembly assembly = Assembly .LoadFrom( @"C:\ TestProject.dll" ); Assembly assembly = Assembly .LoadFrom( @"C:\ TestProject.dll" );
if (assembly != null ) { if (assembly != null ) {
@@ -308,11 +300,10 @@ Console .ReadLine();
} }
``` ```
.
Wenn man nun die Properties mit Werten belegen möchte, so muss man analog zur Methode PropertyInfo.GetValue, die Methode PropertyInfo. SetValue verwenden. Wenn man nun die Properties mit Werten belegen möchte, so muss man analog zur Methode PropertyInfo.GetValue, die Methode PropertyInfo. SetValue verwenden.
``` ```csharp
if (type.Name.Equals( "TestClass" )) { if (type.Name.Equals( "TestClass" )) {
Console .WriteLine( " ++ {0}" , type.Name); Console .WriteLine( " ++ {0}" , type.Name);
PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic PropertyInfo [] propertyInfos = type.GetProperties( BindingFlags .Public | BindingFlags .NonPublic
@@ -326,7 +317,6 @@ Console .WriteLine( " ++ {0} = {1}" , info.Name, info.GetValue(obj, null ));
} }
``` ```
.
## Wozu braucht man jetzt aber nun die Reflection? ## Wozu braucht man jetzt aber nun die Reflection?
-2
View File
@@ -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**
-2
View File
@@ -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)**
+4 -5
View File
@@ -28,7 +28,7 @@ Als Vorteile gegenüber z.B. MogileFS, CouchDB zählt für uns vor allem, dass k
Die Nutzung selbst ist denkbar einfach. Folgendes Mitarbeiter-Objekt möchte ich speichern. Die Nutzung selbst ist denkbar einfach. Folgendes Mitarbeiter-Objekt möchte ich speichern.
``` ```csharp
public class Mitarbeiter public class Mitarbeiter
{ {
public int Id { get ; set ; } public int Id { get ; set ; }
@@ -36,7 +36,6 @@ public string Name { get ; set ; }
public string Vorname { get ; set ; } public string Vorname { get ; set ; }
public string Ort { get ; set ; } public string Ort { get ; set ; }
public string Plz { get ; set ; } public string Plz { get ; set ; }
72
public string VollerName public string VollerName
{ {
get { return string .Format( {0} {1} , Vorname, Name); } get { return string .Format( {0} {1} , Vorname, Name); }
@@ -46,7 +45,7 @@ get { return string .Format( “{0} {1}” , Vorname, Name); }
Dazu nutzt man die Put-Methode der StupidDB. Dazu nutzt man die Put-Methode der StupidDB.
``` ```csharp
IStupidDB stupidDB = new StupidDB ( test ); IStupidDB stupidDB = new StupidDB ( test );
Mitarbeiter mitarbeiter = new Mitarbeiter Mitarbeiter mitarbeiter = new Mitarbeiter
{ {
@@ -61,7 +60,7 @@ stupidDB.Put( “TestPartition” , “EinMitarbeiter” , mitarbeiter );
Um das gleiche Objekt wieder zu lesen, nutzt man mal eine Get Methode. Die Parameter sind Partition und Key: Um das gleiche Objekt wieder zu lesen, nutzt man mal eine Get Methode. Die Parameter sind Partition und Key:
``` ```csharp
Mitarbeiter mitarbeiterAusgelesen = Mitarbeiter mitarbeiterAusgelesen =
( Mitarbeiter )stupidDB.GetObject( ( Mitarbeiter )stupidDB.GetObject(
TestPartition , TestPartition ,
@@ -77,7 +76,7 @@ mitarbeiterAusgelesen.VollerName);
Die StupidDB selbst wird über die app.config definiert. Die StupidDB selbst wird über die app.config definiert.
``` ```csharp
< configuration > < configuration >
< configSections > < configSections >
< section name = stupidDB type = Aztec.StupidDB.DBConfigCollection, Aztec.StupidDB, Version=1.0.0.0, Culture=neutral allowLocation = true allowDefinition = Everywhere /> < section name = stupidDB type = Aztec.StupidDB.DBConfigCollection, Aztec.StupidDB, Version=1.0.0.0, Culture=neutral allowLocation = true allowDefinition = Everywhere />
-2
View File
@@ -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**
+5 -23
View File
@@ -18,17 +18,15 @@ Nehmen wir zum Beispiel ein einfaches Formular. Nach Absenden des Formulars soll
Und nun zum Quellcode: Die automatisch erstellte .designer.cs muss entfernt werden, was man sowieso tun sollte, da man automatisch erstellten Code also Code, den keiner außer Microsoft unter Kontrolle hat vermeiden sollte. Und nun zum Quellcode: Die automatisch erstellte .designer.cs muss entfernt werden, was man sowieso tun sollte, da man automatisch erstellten Code also Code, den keiner außer Microsoft unter Kontrolle hat vermeiden sollte.
Die Inhalte der .designer.cs also die Definitionen der Web-Elemente werden in der Klasse als `Public Properties` erstellt und instantiiert, um bei Zugriffen wie `TextBox.Text` keine NullReferenceException zu bekommen. Die Inhalte der .designer.cs also die Definitionen der Web-Elemente werden in der Klasse als `Public Properties` erstellt und instantiiert, um bei Zugriffen wie `TextBox.Text` keine NullReferenceException zu bekommen.
``` ```csharp
using System; using System;
using System.Web.UI.HtmlControls; using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls; using System.Web.UI.WebControls;
using framework.Testable.Web.UI; using framework.Testable.Web.UI;
5
namespace TestableWebForm namespace TestableWebForm
{ {
public class DefaultPage : System.Web.UI. Page public class DefaultPage : System.Web.UI. Page
{ {
10
#region Controls #region Controls
public HtmlForm Formular; public HtmlForm Formular;
public TextBox Value1 = new TextBox (); public TextBox Value1 = new TextBox ();
@@ -40,8 +38,7 @@ public Button Submit = new Button ();
Um das Verhalten testen zu können, haben wir Adapter für die Klassen `System.Web.UI.Page`, `System.Web.HttpRequest` und `System.Web.HttpResponse` geschrieben, und zwar für die Properties und Methoden die uns vorerst interessieren: z.B. `Page.IsPostBack, Page.Request, Response.Redirect(string url, bool endResponse)`. Bei der Benennung haben wir einfach den Namespace `System` mit `framework.Testable` ersetzt und wir haben natürlich zu jedem Testable-Objekt einen Interface erstellt. Um das Verhalten testen zu können, haben wir Adapter für die Klassen `System.Web.UI.Page`, `System.Web.HttpRequest` und `System.Web.HttpResponse` geschrieben, und zwar für die Properties und Methoden die uns vorerst interessieren: z.B. `Page.IsPostBack, Page.Request, Response.Redirect(string url, bool endResponse)`. Bei der Benennung haben wir einfach den Namespace `System` mit `framework.Testable` ersetzt und wir haben natürlich zu jedem Testable-Objekt einen Interface erstellt.
``` ```csharp
1
namespace framework.Testable.Web namespace framework.Testable.Web
{ {
namespace UI namespace UI
@@ -52,31 +49,26 @@ bool IsPostBack{ get ; }
IHttpRequest Request{ get ; set ; } IHttpRequest Request{ get ; set ; }
IHttpResponse Response{ get ; set ; } IHttpResponse Response{ get ; set ; }
} }
12
public class Page : IPage public class Page : IPage
{ {
private readonly System.Web.UI. Page m_page; private readonly System.Web.UI. Page m_page;
private IHttpRequest m_request; private IHttpRequest m_request;
private IHttpResponse m_response; private IHttpResponse m_response;
18
public Page( System.Web.UI. Page page ) public Page( System.Web.UI. Page page )
{ {
m_page = page; m_page = page;
m_request = new HttpRequest ( m_page ); m_request = new HttpRequest ( m_page );
m_response = new HttpResponse ( m_page ); m_response = new HttpResponse ( m_page );
} }
25
public bool IsPostBack public bool IsPostBack
{ {
get { return m_page.IsPostBack; } get { return m_page.IsPostBack; }
} }
30
public IHttpRequest Request public IHttpRequest Request
{ {
get { return m_request; } get { return m_request; }
set { m_request = value ; } set { m_request = value ; }
} }
36
public IHttpResponse Response public IHttpResponse Response
{ {
get { return m_response; } get { return m_response; }
@@ -91,7 +83,7 @@ Um alle gemockte Objekte setzen zu können, haben wir unserer `Page`-Klasse auch
Das war ungefähr alles: in der Seite nutzt man dann anstelle der eigenen Request und Response-Objekten die `Testable`-Objekte. Das war ungefähr alles: in der Seite nutzt man dann anstelle der eigenen Request und Response-Objekten die `Testable`-Objekte.
``` ```csharp
public class DefaultPage : System.Web.UI. Page public class DefaultPage : System.Web.UI. Page
{ {
@@ -100,7 +92,6 @@ public void SetTestableObjects( IPage page )
{ {
m_page = page; m_page = page;
} }
24
public void Page_Load( object sender, EventArgs e ) public void Page_Load( object sender, EventArgs e )
{ {
if (m_page == null ) m_page = new Page ( this ); if (m_page == null ) m_page = new Page ( this );
@@ -115,13 +106,12 @@ Result.Text = ( ( Convert .ToInt32( Value1.Text ) + Convert .ToInt32( Value2.Te
Damit ist die Web-Anwendung bereit zum Testen. So schaut zum Beispiel ein Test für das Laden der Seite aus: Damit ist die Web-Anwendung bereit zum Testen. So schaut zum Beispiel ein Test für das Laden der Seite aus:
``` ```csharp
using framework.Testable.Web; using framework.Testable.Web;
using framework.Testable.Web.UI; using framework.Testable.Web.UI;
using NUnit.Framework; using NUnit.Framework;
using Rhino.Mocks; using Rhino.Mocks;
using TestableWebForm; using TestableWebForm;
6
namespace Tests namespace Tests
{ {
[ TestFixture ] [ TestFixture ]
@@ -131,7 +121,6 @@ private IPage m_page;
private IHttpRequest m_request; private IHttpRequest m_request;
private IHttpResponse m_response; private IHttpResponse m_response;
private DefaultPage m_defaultPage; private DefaultPage m_defaultPage;
16
[ SetUp ] [ SetUp ]
public void Init() public void Init()
{ {
@@ -141,18 +130,14 @@ m_response = MockRepository .GenerateStub< IHttpResponse >();
m_page.Request = m_request; m_page.Request = m_request;
m_page.Response = m_response; m_page.Response = m_response;
} }
26
[ Test ] [ Test ]
public void PageLoad_Loading_EmptyFields() public void PageLoad_Loading_EmptyFields()
{ {
// Arrange // Arrange
31
32
// Act // Act
m_defaultPage = new DefaultPage (); m_defaultPage = new DefaultPage ();
m_defaultPage.SetTestableObjects(m_page); m_defaultPage.SetTestableObjects(m_page);
m_defaultPage.Page_Load( null , null ); m_defaultPage.Page_Load( null , null );
37
// Assert // Assert
Assert .IsEmpty( m_defaultPage.Value1.Text ); Assert .IsEmpty( m_defaultPage.Value1.Text );
Assert .IsEmpty( m_defaultPage.Value2.Text ); Assert .IsEmpty( m_defaultPage.Value2.Text );
@@ -162,7 +147,7 @@ Assert .IsEmpty(m_defaultPage.Result.Text);
Und so für PostBack inklusive QueryString-Parameter: Und so für PostBack inklusive QueryString-Parameter:
``` ```csharp
[ Test ] [ Test ]
public void PageLoad_PostBackWithRequestValue_ResultIsCorrect() public void PageLoad_PostBackWithRequestValue_ResultIsCorrect()
{ {
@@ -172,14 +157,12 @@ const int value2 = 2;
const int value3 = 3; const int value3 = 3;
m_request.Expect( a => a.GetParamValue( "multiple" ) ).IgnoreArguments().Repeat.Twice().Return( value3.ToString() ); m_request.Expect( a => a.GetParamValue( "multiple" ) ).IgnoreArguments().Repeat.Twice().Return( value3.ToString() );
m_page.Expect( a => a.IsPostBack ).Return( true ); m_page.Expect( a => a.IsPostBack ).Return( true );
72
// Act // Act
m_defaultPage = new DefaultPage (); m_defaultPage = new DefaultPage ();
m_defaultPage.SetTestableObjects( m_page ); m_defaultPage.SetTestableObjects( m_page );
m_defaultPage.Value1.Text = value1.ToString(); m_defaultPage.Value1.Text = value1.ToString();
m_defaultPage.Value2.Text = value2.ToString(); m_defaultPage.Value2.Text = value2.ToString();
m_defaultPage.Page_Load( null , null ); m_defaultPage.Page_Load( null , null );
79
// Assert // Assert
Assert .IsTrue( m_defaultPage.Result.Text == ( (value1 + value2)*value3 ).ToString() ); Assert .IsTrue( m_defaultPage.Result.Text == ( (value1 + value2)*value3 ).ToString() );
} }
@@ -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"/>
+1 -3
View File
@@ -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ß
+8 -8
View File
@@ -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"/>
+3
View File
@@ -0,0 +1,3 @@
---
title: "Schlagwörter"
---
+5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Architektur"
---
Softwarezellen, Design by Contract und Plug-In-Architekturen — Gedanken zu Struktur, Verträgen und klarem Aufbau von Software.
+5
View File
@@ -0,0 +1,5 @@
---
title: "ASP-Classic"
---
SOAP-Kommunikation zwischen ASP-Classic und .NET-Webservices — technische Anleitungen für den Legacy-Betrieb.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Assemblies"
---
Grundlagen zu .NET-Assemblies: Aufbau, Laden und Reflexion für dynamische Architekturen.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Assembly"
---
Dynamisches Laden von Assemblies und Reflection — Schlüsseltechnologien für flexible Plug-In-Systeme.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Basics"
---
Die Grundlagen der professionellen Software-Entwicklung — von sauberer Codebasis bis zu bewährten Coding-Practices.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Buch"
---
Buchbesprechungen und Zusammenfassungen — von „Clean Code" über „Code Complete" bis zu „The Pragmatic Programmer".
+5
View File
@@ -0,0 +1,5 @@
---
title: "Buchempfehlung"
---
Unsere Empfehlungen für pflichtlektürenwertige Bücher zur software-Entwicklung.
+5
View File
@@ -0,0 +1,5 @@
---
title: "C#"
---
Praktische C#-Entwicklung: Anonyme Datentypen, dynamisches Assembly-Laden, Plug-In-Patterns und Design by Contract mit Spec#.
+5
View File
@@ -0,0 +1,5 @@
---
title: "CCD"
---
Der Clean Code Developer — von der Yellow Brick Road bis zu SOLID, DRY und professioneller Code-Kultur.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Clean Code"
---
Sauberer, lesbarer und wartbarer Code — unsere Erfahrungen mit Clean Code, Refactoring und bewährten Development-Practices.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Coding"
---
Buch-Zusammenfassungen und Praxis-Tipps für besseres Programmieren — direkt aus dem Alltag als .NET-Entwickler.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Community"
---
Open Spaces, BarCamps und User Groups — unser Austausch mit der .NET-Entwickler-Community.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Contract-First"
---
Beim MVP-Pattern mit WinForms: Verträge zuerst definieren, dann implementieren — für testbare und klare Architekturen.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Datenbank"
---
Von relationalen Datenbanken zu NoSQL — StupidDB als schemaloser Objektspeicher für .NET.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Datenschutz"
---
Datenschutz im Social Web — Diskussionen und Erfahrungen von BarCamps und in der Community.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Facebook"
---
Eindrücke und Diskussionen rund um soziale Plattformen wie Facebook — vom BarCamp bis zur Datenschutzdebatte.
+5
View File
@@ -0,0 +1,5 @@
---
title: "HowTo"
---
Praktische Schritt-für-Schritt-Anleitungen: Reflection, dynamisches Assembly-Laden, anonyme Datentypen und Plug-In-Architekturen.
+5
View File
@@ -0,0 +1,5 @@
---
title: "HTML"
---
HTML-Techniken in der Praxis: Code-Formatierung für Webseiten und die Umwandlung von Visual-Studio-Quellcode.
+5
View File
@@ -0,0 +1,5 @@
---
title: "MVC"
---
ASP.NET MVC und Model-View-Presenter — Erfahrungsberichte, Vorträge und Implementierungsbeispiele.
+5
View File
@@ -0,0 +1,5 @@
---
title: "MVP"
---
Das Model-View-Presenter-Pattern in der Praxis — mit Contract-First-Ansatz in WinForms.
+5
View File
@@ -0,0 +1,5 @@
---
title: "NoSQL"
---
NoSQL mit StupidDB — ein schemaloser Objektspeicher für .NET, einfach und flexibel.
+5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Patterns"
---
Entwurfsmuster und WebCast-Empfehlungen — von async-Patterns bis zu bewährten Solution-Patterns.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Plug-In"
---
Plug-In-Architekturen mit Reflection und dynamischem Assembly-Laden — für erweiterbare .NET-Anwendungen.
+5
View File
@@ -0,0 +1,5 @@
---
title: "prio.conference"
---
Tagungsbericht von der prio.conference 2009 in München — Sessions zu UI-Entwicklung, Usability und agiler Softwareentwicklung.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Refaktorisierung"
---
Refactoring mal anders erklärt — mit Metaphern aus dem Alltag und konkreten Beispielen aus der Praxis.
+5
View File
@@ -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?
+5
View File
@@ -0,0 +1,5 @@
---
title: "Schemalos"
---
Kein starres Schema, volle Flexibilität — StupidDB als NoSQL-Ansatz für die einfache Objektpersistierung.
+5
View File
@@ -0,0 +1,5 @@
---
title: "SCRUM"
---
Reflexion über unsere SCRUM-Einführung — was lief gut, was lief anders, und warum „Scrumbut" kein Schimpfwort ist.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Social Web"
---
Dezentrale soziale Protokolle, Facebook und Datenschutz — Diskussionen vom BarCamp Bodensee.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Sonne"
---
Sommer, Sonne und BarCamp — Eindrücke von Events und Community-Treffen im Freien.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Sourcecode"
---
Code-Formatierung und -Übertragung: Wie man Quellcode aus Visual Studio sauber in HTML und Blog-Artikel bekommt.
+5
View File
@@ -0,0 +1,5 @@
---
title: "StupidDB"
---
Unser Open-Source-Framework zur einfachen Objektpersistierung auf dem Dateisystem — vom ersten Entwurf bis zu NoSQL-Konzepten.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Unit Tests"
---
Unit-Tests in der Praxis: Strategien für WebForms, nicht-öffentliche Methoden und Design by Contract.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Veranstaltung"
---
Open Spaces, BarCamps, User Groups und WebCamps — Berichte von .NET-Veranstaltungen und Community-Treffen.
+5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Webapplication"
---
Architektur und Testbarkeit von Webanwendungen — von Schichtenarchitektur bis zu Workarounds für bessere Unit-Tests.
+5
View File
@@ -0,0 +1,5 @@
---
title: "WebCamp"
---
Bericht vom Microsoft WebCamp in München — zwei Tage zu MVC2, Entity Framework und jQuery.
+5
View File
@@ -0,0 +1,5 @@
---
title: "WebCast"
---
Empfehlenswerte WebCasts und Podcasts für Entwickler — von Dime Casts bis zu Hanselminutes.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Webservice"
---
SOAP-Kommunikation und Webservice-Integration — Technische Anleitungen für den Austausch zwischen .NET und anderen Systemen.
+5
View File
@@ -0,0 +1,5 @@
---
title: "WinForms"
---
Windows Forms in der Praxis — vom MVP-Pattern bis zu testbaren Desktop-Anwendungen.
+5
View File
@@ -0,0 +1,5 @@
---
title: "Wordpress"
---
WordPress als Blog-Plattform — Erfahrungen mit Code-Export, Theme-Anpassungen und Content-Migration.
+12 -5
View File
@@ -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>&copy; 2009-{{ now.Format "2006" }} build broken &middot; <a href="{{ "/impressum/" | relURL }}">Impressum</a> &middot; archiviert als statische Seite &middot; part of the <a href="https://hub.moonweb.org">moonweb.org</a> homelab.</p> <p>&copy; 2009-{{ now.Format "2006" }} build broken &middot; <a href="{{ "/impressum/" | relURL }}">Impressum</a> &middot; archiviert als statische Seite &middot; part of the <a href="https://www.moonweb.org">moonweb.org</a> homelab.</p>
</footer> </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>
+25
View File
@@ -0,0 +1,25 @@
{{- $pages := where .Site.RegularPages "Section" "!=" "" }}
{{- $pages = where $pages "File.ContentBase" "!=" "impressum" }}
{{- $limit := .Site.Config.Services.RSS.Limit }}
{{- if ge $limit 1 }}
{{- $pages = $pages | first $limit }}
{{- end }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ .Site.Title }}</title>
<link>{{ .Site.BaseURL }}</link>
<description>{{ with .Site.Params.description }}{{ . }}{{ end }}</description>
<generator>Hugo -- gohugo.io</generator>
{{- with .Language.Locale }}<language>{{ . }}</language>{{ end }}
{{- range $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</pubDate>
<guid>{{ .Permalink }}</guid>
<description>{{ .Summary | html }}</description>
{{- with .Params.author }}<author>{{ . }}</author>{{ end }}
</item>
{{- end }}
</channel>
</rss>
+1
View File
@@ -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
View File
@@ -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 }}