Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Basta! Spring 2010

Basta Spring 2010Ich habe es dieses Jahr trotz der weiten Entfernung endlich auf die Basta! Spring geschafft.
In der Zeit der Hauptkonferenz vom 23 - 25 Februar gab es über 100 verschiedene Sessions aus denen man frei wählen konnte.

Leider war es nicht möglich auf jeder gleichzeitig zu sein - trotzdem haben sich alle besuchten Sessions gelohnt!

Es waren viele bekannte Leute bzw. Sprecher dort um Vorträge zu den unterschiedlichsten Themen im .Net Bereich zu halten.

Einige besonders interessante Themen waren funktionale Sprachen wie F#, Software Architektur, OR Mapper wie NHibernate oder Entity Framework 4, TDD und BDD, Silverlight 4, WIF, Paralellisierung und viele weitere mehr.

Wer etwas verpasst hat oder mehr dazu wissen möchte, kann gerne auf Jürgens Blog vorbeischauen. Trotz der knappen Zeit ist es ihm gelungen zu jeder Session einen Blog Eintrag zu schreiben.

Die Veranstaltung fand im Maritim Rhein Hotel in Darmstadt statt.



Für Verpflegung wurde ausreichend gesorgt - sowohl gutes Essen als auch ein Bierchen am Abend zur letzten "Night Session" waren dabei ;-)

Basta Konferenz SaalBasta Konferenz Saal

Leider war, wie Jürgen schon erwähnt hat, die Abschluss Veranstaltung etwas zu knapp. Es waren nicht mehr viele Teilnehmer dort.
Zum Abschluss haben Jürgen, Peter, Gregor, Robert und ich nochmals beim Bierchen die Basta revue passiert.

Im Großen und Ganzen gesehen war die Teilnahme auf jeden Fall Wert.
Dabei habe ich viele interessante Leute kennen gelernt und klarerweiße auch viele neue Inputs geholt.

Jetzt wird erst einige Zeit vergehen, bis man das gesammelte Material auch verarbeiten und umsetzten kann.

Wenn die Möglichkeit besteht, war es sicher nicht meine letzte Basta!

Eingetragen von Roberto | 0 Kommentare
Abgelegt unter:

Abstraktion

AbstraktionAm 13. Oktober 2008 haben Peter Bucher und Golo Roden unter dem Titel "Noch Fragen, Bucher? Ja, Roden!" angekündigt, jeweils zum ersten eines jeden Monats einen Kommentar zu einem vorab gemeinsam gewählten Thema verfassen zu wollen. Heute, am 1. März 2010, ist es nun wieder so weit, und das Thema für diesen Monat lautet:

Abstraktion

Die beiden haben mich netterweise gefragt ob ich dieses Mal als "Gast-Mitstreiter" dabei bin, und so haben wir drei uns unabhängig voneinander im Vorfeld unsere Gedanken gemacht, wie wir diesem Thema gegenüberstehen. Peters und Golos Kommentare finden sich zeitgleich in ihren Blogs, folgend nun meine Meinung zu diesem Thema:

Das Prinzip der Abstraktion ist im Grunde nichts anderes als das Weglassen von Einzelheiten und das Überführen auf etwas Allgemeines oder Einfacheres (Generalisieren). So jedenfalls die Definition.

Auch in der Objektorientierten Programmierung findet dieses Prinzip Einsatz. Durch die Abstraktion von Aktionen oder Eigenschaften wird versucht, Redundanzen zu vermeiden.

Außerdem vereinfacht uns Abstraktion das Leben durch Verringerung der Komplexität.

Beispiele für Abstraktion
  • Auslagerung von Codezeilen in eine benannte (zentralere) Funktion
  • Einführung von generischen Klassen
  • Unterklassen
  • Interfaces
  • Vererbung
  • Modularisierung der Anwendung

Grundsätzlich wird in der Software Programmierung zwischen zwei Arten der Abstraktion unterschieden:

Funktionale Abstraktion (Control abstraction)

In der funktionalen Abstraktion wird eine Anwendung an Hand von funktionalen Merkmalen in individuelle Komponenten aufgebrochen, wobei die einzelnen Komponenten jeweils eine logisch-semantisch zusammengehörige Einheit bilden.

Bspw. so eine drei Schichten Architektur

Funktionale Abstraktion

Jede der einzelnen Schichten erfüllt eine bestimmte Funktion und auf diese sollte sie sich beschränken. Wie das intern abgehandelt wird, interessiert einzig und allein diese selbst.

Die Grafik zeigt die Präsentationsschicht (bspw. Website), die auf die Business Logik zugreift um bspw. eine Auflistung aller Kunden zu bekommen. Wenn ein Entwickler das grafische UI programmiert, sollte er die Möglichkeiten der Business Schicht verstehen, aber nicht deren internen Aufbau.

Die Business Schicht wiederum dient als Schnittstelle zur Datenschicht. Sie beinhaltet lediglich Services, die die gewünschten Daten aus der Datenschicht holen und zur Verfügung stellen. In welcher Art und Weiße die Daten geholt werden, ist, wie weiter oben bereits erwähnt, der Präsentation egal.

Woher die Daten kommen, ob und welche Datenbank verwendet wird, welcher OR Mapper im Einsatz ist, … interessiert nur die Datenschicht.
Alle Schichten sind voneinander unabhängig. Dies senkt die Komplexität der Anwendung und vereinfacht das ändern zentraler Funktionalitäten.

Wenn gesagt wird: Belästige mich nicht mit Details – wird versucht zu abstrahieren.

Datenabsraktion (Data abstraction)

Das klassische Beispiel hierfür sind mehrere Entitäten

Daten Abstraktion

Jede dieser einzelnen Entitäten beinhaltet Standartfelder wie Id, Erstell- und Änderungsdatum.

Es wäre suboptimal diese getrennt in den einzelnen Entitäten einzufügen. Viel besser ist eine etwas mehr zentralere Klasse (Entity), die bestimmte, für alle gemeinsame Eigenschaften beinhaltet um  somit ein wenig Redundanz zu vermeiden.

In C# werden mit dem „abstract“- Modifizier(in vb.Net MustInherit) zentrale Klassen erstellt, sprich sie dienen nur als Basisklassen und müssen implementiert werden.

Wie viel Abstraktion ist zu viel Abstraktion?

Je detailreicher die Strukturen bzw. Modelle werden (je weniger Abstraktion sie verwenden), desto besser ist die Akzeptanz. Je weniger Details die einzelnen Modelle der Anwendung beinhalten (je mehr Abstraktion sie verwenden), desto größer ist die Flexibilität und Wiederverwendbarkeit.

Das Abstraktionsprinzip besagt, Abstraktion sollte dann verwendet werden, wenn damit Redundanzen (üblich im Code) vermieden werden können.
Die Softwareentwicklung strebt ja bekanntlich nach immer höheren Stufen der Abstraktion. Aber mehr ist nicht immer besser. Wer ganze Stockwerke aus Abstraktionsebenen baut, vergisst, dass diese nicht nur von Maschinen betreten werden.

Mehrere Abstraktionen bedeuten meist einen höheren Aufwand und sind außerdem schwieriger zu verstehen.

Zu Beginn eines Projektes sind Abstraktionen teilweise schnell implementiert, allerdings müssen sich diese erst bewähren:
Wird in einem späteren Projektzyklus klar, dass eine Abstraktion nicht korrekt ist, können sehr hohe Änderungskosten anfallen – ein Alptraum für jeden Entwickler.

Um ein neues Problem zu lösen gerät mancher Entwickler in die Versuchung eine zusätzliche Abstraktionsschicht einzubauen. Abstraktionen an sich sind nicht das Problem, sondern vielmehr die (zu komplexe oder falsche) Denkweise des Entwicklers, der sich damit zusätzliche Probleme einfängt.

Ein Beispiel dazu:

Zwei Entitäten Kunden und Benutzer sind gegeben. Jetzt fällt einem Entwickler ein, dass beides Personen sind und beide das Feld Name haben. Es ist eine Basisklasse „Person“ gegeben und das „Name“ Feld wird dorthin verschoben.
Jetzt kommt ein weiterer Entwickler, der der Meinung ist, dass das Feld „Name“ bei bestimmten Kunden eigentlich die Kundennummer ist und abstrahiert das Feld zu „Kundennummer“.
Was zurück bleibt ist eine nicht mehr klare Bedeutung der einzelnen Felder.

Mein Fazit

Wie bei so vielem im Leben ist auch hier eine gesunde Mischung gefragt.
Abstraktion gehört zu den Urbausteinen der Softwareprogrammierung. Heute wäre es nahezu undenkbar eine Anwendung ohne zu entwickeln.

Wie viel Abstraktion verwendet wird, hängt von vielen Faktoren ab. Unter anderem den Anforderungen, der Erfahrung des Teams, Erweiterbarkeit, etc.
Wenn bspw. von Anfang an vorgesehen wird, dass ein Kunde ein Verkäufer werden kann, sollte vielleicht auch vorgesehen werden, dass dieser zu einem Mars Mensch mutieren kann.
Klar, diese „neverending abstraction“ ist immer eine Frage des Aufwands.

Ich kenne die Frage sehr wohl, was man wie in Zukunft machen könnte oder vorsehen sollte. In den meisten Fällen hat sich herausgestellt – Eierlegende Wollmilchsäue sind nur selten die Beste Lösung. Abstraktion ist dafür gedacht, die Komplexität einer Anwendung zu verringern und nicht in den Sternen zu treiben.

Eingetragen von Roberto | 0 Kommentare
Abgelegt unter: ,

DI / IoC Container LightCore Teil 3: Registrierung von Generics

LightCore Generics RegistrationIm dritten Teil der Post Serie wird die bisher angewandte drei Schichten Struktur etwas erweitert, sodass diese durch das hinzufügen von generischen Klassen etwas mehr flexibel wird.

1. LightCore – Einführung
2. LightCore – Registrierung über Xml Module
3. LightCore – Registrierung von Generics

Generics sind nichts anderes als Klassen, bei denen der Nutzer die Datentypen, die der Typ verarbeiten soll, vorgeben kann.

Es kann durchaus Sinn machen, bei Projekten die Repositories generisch zu halten, da sich diese immer auf dieselbe Datenquelle beziehen(bspw. bei Verwendung des NHibernate OR-Mappers) und sie sich lediglich von den angeforderten Typen unterscheiden.

LightCore bietet zwei Möglichkeiten, generische Typen zu registrieren.

  Closed Types

Eine generische Klasse, die schon einen Typparameter hat.
IRepository<User> zu Repository<User> registrieren.

Die Registrierung und Auflösung sieht in dem Fall so aus:

builder.Register<IRepository<User>, Repository<User>();
container.Resolve<IRepository<User>>();


Der Nachteil dieser Variante liegt darin, dass jedes Repository getrennt registriert werden muss (eines für den User, eines für die UserGroup…). Dies wär in dem Fall suboptimal, da sie von den Funktionen her identisch sind.

  Open Types

Eine generische Klasse, die keinen bestimmten Parameter hat.
Der Kontrakt, in dem Fall IRepository<> hätte die Implementation Repository<>.

Der Vorteil dabei ist die Auflösung (on-the-fly) auf einem beliebigen Typparameter.
So wäre IRepository<User> und IRepository<UserGroup>.

In dem Fall muss das Repository nur ein einziges Mal registrieren werden und kann mit beliebigem Typ auflöst werden.

Wenn das Projekt ein generisches Repository mit viele verschiedene Typen dazu hat, ist der open type die erste Wahl.

Als Beispiel wird wieder eine Solution mit einer klassischen drei Schichten Architektur verwendet.

Um das Ganze noch etwas zu vertiefen wird eine vierte Schicht „Core“ hinzugefügt. Diese beinhaltet die zentralen Interfaces wie bspw. IRepository oder IService.

LightCore Generics Registration Project Tree

Das Repository muss wie oben beschrieben nur einmal registriert werden:

container.Register(typeof(IRepository<>), typeof(Repository<>));

Jetzt könnten alle gewünschten Typen (Im Beispiel user und usergroup) instanziiert werden:

container.resolve<IRepository<User>>();
container.resolve<IRepository<UserGroup>>();


Fazit:

Durch die Unterstützung von generischen Klassen ist Lightcore für mich interessant gewordern und ist bereits in einer Anwendung im Einsatz. Es funktioniert bis dato Problemfrei und ohne merkbare Performance Verluste. Es bietet genau das, was ich zur Zeit benötige. Somit bin ich gespannt auf dessen zukünftige Erweiterungen.

Anbei wie immer das Beispiel nicht vergessen! :-)

Eingetragen von Roberto | 2 Kommentare
Abgelegt unter: , , ,

Attachment(s): LightCore.DemoGenerics.zip

DI / IoC Container LightCore Teil 2: Registrierung über Xml Modul

LightCore Xml Registration moduleWie bereits im ersten Teil erwähnt, wird es einer mehr Schichten Architektur immer wichtiger, Abhängigkeiten zu minimieren. Dabei einen ersten Schritt zu setzen, ist die Implementierung eines IoC Containers. Dieser hilft dabei, den Code in den diversen Projekten unabhängiger zu halten.

1. LightCore – Einführung
2. LightCore – Registrierung über Xml Module
3. LightCore – Registrierung von Generics

Mich persönlich hat Peter Bucher mit seinem neusten Projekt LightCore darauf aufmerksam gemacht. Anfangs war dies nur als Unit Testing Spielwiese gedacht, hat sich aber zu einem konkurrenzfähigen Micro Kernel entwickelt. Dessen weitere Entwicklung wird in Zukunft per Community gesteuert. Der Source Code wurde veröffentlicht und ist auf jeden Fall einen Blick Wert! (Stichwort: Codequalität)



Zurück zum Thema. Viele werden sich denken, was bringt mir das Ganze eigentlich?
  • Abhängigkeiten verringern und Kontrolle der Abhängigkeiten (Interface / Basisklasse - also Abstraktion) und dadurch auf die Instanziierung (Die sonst auf den konkreten Typen geht), rausziehen.
  • Dadurch bessere Testbarkeit und Aufbrechung der Anwendung in mehr Einzelteile, wodurch die Komplexität sinkt.
  • Außerdem globale Konfigurierbarkeit, was genutzt werden soll um Änderungen an vielen, unnötigen Stellen vorzunehmen.
Nun wird anhand eines Beispiels wird gezeigt, wie man Lightcore in einer drei Schichten Architektur einsetzen bzw. integrieren kann.
Gleichzeitig wird die Registrierung über das integrierte Xml Modul erklärt

Was eine drei Schichten Architektur (Tier 3 layer) ist, erklärt Robert Mühsig in seinem Post.

Das folgende Beispiel wird eine Solution mit mehreren Projekten beinhalten, die eine Anwendung mit mehreren Schichten darstellt.

Die Klassische Struktur sieht wie folgt aus:
  • Data Access Layer
  • Business Layer
  • Presentation Layer
Folglich könnten die Projekte so benannt werden:

Data: beinhaltet eine User Klasse sowie das dazugehörige Repository.

Service: beinhaltet einen User Service mit dem Repository als Member, der per Constructor Injection injiziert wird.

Web: Die Präsentation

LightCore Xml Registration Project Tree

Im ersten Teil des Posts wurde gezeigt, wie man Kontrakte einfach per code (global.asax) registrieren kann. Der erste Schritt wird sein, die Registrierung in die Web/App.config bzw. einer eigenen Konfigurationsdatei auszulagern.

Wer achte gibt sieht, dass beim heruntergeladenen Paket unter Anderem eine XML Schema Datei (LightCore.xsd), sowie eine Beispiel Konfiguration (SampleConfiguration.xml) dabei ist.
Nun die Datei LightCore.xsd dem aktuellen Web Projekt hinzufügen.

Es kann eine eigene Konfigurationsdatei erstellt werden, oder aber einfach den entsprechenden Block in die Web/App.config hinzufügen.
Wer sich für letzteres entscheidet, den Block, der innerhalb der SampleConfiguration.xml zu finden ist, einfach in den <configuration> Abschnitt packen.

<configuration>
    …
    <LightCoreConfiguration xmlns="clr-namespace:LightCore.Configuration;assembly=LightCore.Configuration">


Als erstes kommt die Einstellung über den genutzten Lebenszyklus, also ob Singleton, Transient oder HttpRequest.

<LightCoreConfiguration.TypeAliases>
    <TypeAlias Alias="HttpRequest" Type="LightCore.Integration.Web.HttpRequestLifecycle, LightCore.Integration.Web"/>
</LightCoreConfiguration.TypeAliases>


Jetzt die eigentliche Registrierung.
Einfache Registrierungen ohne Gruppen kommen in die LightCoreConfiguration.Registrations Sektion:

<LightCoreConfiguration.Registrations>
    <Registration Name="MyRepository" ContractType="MyAssembly.IUserRepository, MyAssembly" ImplementationType="MyAssembly.Repository, MyAssembly" />
</LightCoreConfiguration.Registrations>


Sollte selbsterklärend sein, Namensangabe um den registrierten Kontrakt wiederzufinden, der Kontrakttyp sowie die Klasse mit jeweiliger Angabe des Assembly Namens. Optionale Argumente für den Konstruktor können über Arguments=“Value1, Value2“ mitgegeben werden. Wenn die LightCore.xsd im Projekt eingebunden wurde, sollte auch IntelliSense zur Verfügung stehen!

Ist eine gruppierte Registrierung gewollt, um bspw. die Repositories zu sammeln, gibt es auch hierfür eine Sektion:

<LightCoreConfiguration.RegistrationGroups>
    <RegistrationGroup Name="Repositories">
        <RegistrationGroup.Registrations>
            <Registration Name="MyRepository" ContractType="MyAssembly.IUserRepository, MyAssembly" ImplementationType="MyAssembly.Repository, MyAssembly" />
        </RegistrationGroup.Registrations>
    </RegistrationGroup>
</LightCoreConfiguration.RegistrationGroups>


Jetzt wo die Registrierung der Repositories gemacht wurde, muss der Container erstellt und das Konfigurationsmodul zugewiesen werden.

Dies sollte in der Globalen Anwendungsdatei (global.asax) in der Application_Start methode gemacht werden:

protected void Application_Start(object sender, EventArgs e)
{
    var builder = new ContainerBuilder();
    var module = new XamlRegistrationModule();
    builder.RegisterModule(module);
    _container = builder.Build();
}


Wenn gewollt, kann die Konfigurationsdatei im Konstruktor des XamlRegistrationModules angegeben werden:

var module = new XamlRegistrationModule("~/MyConfiguration.xml")

Die Objekte können wie gehabt mit resolve aufgelöst werden

var myRepository = _container.Resolve<IRepository>("MyRepository");

Fazit: Welche Variante ist nun besser?

Registrierung per Code hat den Vorteil, dass weniger Fehler auftreten können.
Da es kompiliert werden muss, wird überprüft ob der Kontrakt implementiert ist oder nicht.

Somit ist es intuitiver und Refactoring-sicher. Wenn eine Klasse umbenannt wird – wird sie auch hier übernommen.

Allerdings hat die Registrierung per Code einen riesen Haken: Es ist eine harte – also feste – Referenz.
Wenn eine Assembly, aus welchem Grund auch immer, ausgetauscht wird, muss das komplette Projekt neu kompiliert werden.

Sollte das bei einer Produktivumgebung der Fall sein, kann das ein großer Nachteil sein. Dadurch muss ein komplettes Deployment gemacht werden.

Was also „besser“ ist, hängt vom Anwendungsfall ab.
Ich persönlich finde die „per Code“ Variante bei nicht allzu vielen Klassen übersichtlich.
Eine gut durchdachte Anwendungsstruktur von Anfang an ist natürlich Voraussetzung :-)

Anbei gibt’s wie immer das oben gezeigte Beispiel als Download.

Eingetragen von Roberto | 5 Kommentare
Abgelegt unter: , , ,

Attachment(s): LightCore.DemoPartTwo.zip

DI / IoC Container LightCore Teil 1: Einführung

LightCore DI/Ioc ContainerIn einer mehrschichtigen Architektur (Oft verwendet die 3-Tier/3 Schichten Architektur) wird es immer wichtiger Abhängigkeiten zu minimieren.
In großen Projekten kann es leicht vorkommen, dass ein Chaos von Abhängigkeiten entsteht.
Nicht alle Abhängigkeiten lassen sich vermeiden, aber viele sind unnötig. Vor allem falsche können die Entwicklung eines Projektes aufhalten.

In den diversen Schichten(Data, Web, …)  den Code unabhängig voneinander zu halten, hilft dem gesamten Prozess ungemein.
Eine Technik, um die Kopplung so gering wie möglich zu halten ist ein Inversion of Control Container. (IoC Container)

Mehr dazu in einem Artikel von Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern

Genauer eingegangen wird auf den frischen IoC Container „LightCore“ von Peter Bucher.
Wie das Wort schon sagt, ist dieser lightweight und kann mit Performance und Einfachheit punkten, muss aber nicht in Sachen Features einpacken.

Eine Feature List von Lightcore finder sich hier.
Weiter Argumente die für LightCore sprechen finden sich in einem Blogeintrag von Golo Roden.

1. LightCore – Einführung
2. LightCore – Registrierung über Xml Module
3. LightCore – Registrierung von Generics

Der Erste Teil der Einführung wird ein einfaches Beispiel mit LightCore behandeln.
Um etwas tiefer in die Materie einzusteigen wird im zweiten Teil gezeigt, wie man mit LightCore die Unabhängigkeit einer 3-Schichten Architektur verbessern kann, wie man mit dem integrierten Registrations-Modul über Xml bzw. XAML arbeitet und wie man Generics registrieren kann.

Einbinden von Lightcore

Wer auf die Entwicklungsversion zugreifen will, muss sich diese mit Subversion laden.
Diese wurde vor allem laut den Prinzipien von CCD (Clean code development) und mit einer kompletten Unit Test Abdeckung entwickelt – somit auf jeden Fall einen Blick Wert!

Für das folgende Beispiel reichen die DLLs, die es ebenfalls hier zum Download gibt.



Als Erstes müssen die gewünschten Kontrakte (Schnittstellen) registriert werden. Dies kann über zwei Varianten erfolgen:
  • Registrierung über Code
  • Registrierung über eine XML Konfigurations- Datei
Wenn die Registrierung codeseitig gewünscht ist, sollte dies in der Globalen Anwendungsdatei (Global.asax) erfolgen

Der Container wird als static member deklariert

private static IContainer _container;

In der Methode Application_Start wird der IoC Container initialisiert.

protected void Application_Start(object sender, EventArgs e)
{
    var builder = new ContainerBuilder();


Es gibt mehrere, optionale Features, die bei der Registrierung angegeben werden können. Für das Beispiel wird ein Logger registriert.

// Einfache Registrierung
builder.Register<ILogger, Logger>();
// Angabe eines Namen, um eine klare Trennung zu behalten
builder.Register<ILogger, Logger>().WithName("MyLogger");
// Angabe einer Gruppe, um bestimmte Kontrakte zu sammeln
builder.Register<ILogger, Logger>().WithGroup("MyGroup");
// Angabe von Argumenten für den Konstruktoraufruf
builder.Register<ILogger, Logger>().WithArguments("Test Argument", true);
// Gemischte Angabe
builder.Register<ILogger, Logger>().WithName("MyLogger").WithArguments("Test Argument", true);


Wenn alles registriert ist, den Container noch erstellen:

_container = builder.Build();

Die Registrierten Kontrakte können manuell über resolve aufgelöst werden

// Einfach
var myLogger = _container.Resolve<ILogger>();
//Mit Namen
var myLogger = _container.Resolve<ILogger>("MyLogger");


Lifecycle

Der Lebenszyklus besagt, ob bei jedem Anfordern einer Instanz über <Container>.Resolve<Icontract>() ein neues Objekt erstellt wird (Identisch zum new-Operator), oder das Objekt wiederverwendet werden kann.

Für die Wiederverwendung von Instanzen werden Folgende Lebenszyklen unterstützt, wobei auch weitere Lebeszyklen geschrieben werden können:
  • Transient
  • Singleton
  • ThreadSingleton
  • HttpRequest
Als Standard wird der transient Lebenszyklus verwendet. Dies besagt, dass bei jedem Aufruf eine neue Instanz der Klasse erstellt wird. Singleton würde bedeuten, dass eine einzige Instanz pro Anwendung erzeugt wird.

Der Lebenszyklus wird einmal pro Kernel wie folgt gesetzt:

builder.DefaultControlledBy<TransientLifecycle>();

Klarer weiße kann man den Lebenszyklus auch pro Registration angeben:

builder.Register<ILogger, Logger>().ControlledBy<SingletonLifecycle>();

Die Lebenszyklen können ohne Probleme beliebig erweitert werden.


Das Ergebnis

Angenommen wir haben den Logger registriert und möchten nun die Instanz in einer normalen Page Klasse

public partial class _Default : System.Web.UI.Page
{
    public ILogger Logger
    {
        get;
        set;
    }


In der Page Load wird der logger nun benötigt:

protected void Page_Load(object sender, EventArgs e)
{
    ILogger logger = this.Logger;
    logger.log("Page Loaded!");
}


Und wie von Magie haben wir eine Instanz der Klasse ohne direkte Erstellung auf der aktuellen Seite.

Was ist passiert?
Ein http Modul in Lightcore macht automatisch ein resolve auf die Eigenschaften, die registriert sind. Somit entfällt die manuelle Auflösung und man ist komplett vom Dependency Injection Container unabhängig.

Damit das auch klappt, muss das Modul in der Web config im Abschnitt <httpModules> hinzugefügt werden:

<httpModules>
    ...
    <add name="LightCoreDependencyInjectionModule" type="LightCore.Integration.Web.DependencyInjectionModule, LightCore.Integration.Web, Version=1.0.0.0, Culture=neutral"/>
</httpModules>


Mit ein bisschen Kreativität kann diese Vorgehensweise in allen möglichen Stellen eingesetzt werden – und schon ist man ein klein wenig Unabhängiger!

Der zweite Teil des Posts wird mit folgenden Themen in Kürze folgen: Registration über Web/App Config oder eigene Konfigurationsdatei, Einsatz in einer 3 Schichten Architektur und Registrierung von Generics!

Anbei gibt’s wie immer das oben angeführte Beispiel!

Eingetragen von Roberto | 4 Kommentare
Abgelegt unter: , ,

Attachment(s): LightCore.DemoPartOne.zip

QUnit – Javascript Unit Test Framework

Wie Peter Bucher bereits in seinem Eintrag erwähnte, macht es überall dort Sinn Unit Tests einzubauen, wo das Verfahren leicht einzubauen ist, ohne sich dadurch das Leben schwer zu machen.

Nicht nur für Asp.Net, sondern auch für Javascript gibt es diverse Test Suiten.
Nicht gewusst? Na dann wird’s Zeit! Letztendlich ist es nicht schön, wenn auf einer Webseite immer diese lästigen Javascript Fehler auftreten und somit bestimmte Funktionen der Seite nicht mehr funktionieren

In diesem Beitrag wird auf QUnit näher eingegangen. Diese wird bspw. zum Testen des JQuery Frameworks verwendet, ist „easy-to-use“ und kann eigentlich alles, was man von einem einfachen Testing Framework erwartet.

Zum starten braucht es nicht viel: Einfach die qunit.js und qunit.css in eine einfache HTML Seite einbinden und los geht’s!

Im Body Tag der Seite das folgende Element hinzufügen:

<ol id="qunit-tests"></ol>

Wie alles beginnt:

test("Das ist mein erster Test", function () {
    ok(true, "Dieser Test war erfolgreich");
});


Die Ausgabe wird nun wie folgt aussehen:

Das ist mein erster Test (0, 1, 1)

Die Zahlen in den Klammern sagen aus, wie viele von den insgesamt durchgeführten „Assertions“  erfolgreich und fehlerhaft waren:

(Anzahl Fehlgeschlagen, Anzahl Erfolgreich, Anzahl Insgesamt)

Neben der „ok assertion“ gibt es noch zwei weitere: equals und same
Equals ist grundsätzlich nichts anderes als ein Vergleich zweier Werte, die als Parameter der Funktion übergeben werden:

test("Der zweite Test", function () {
    expect(2);
    equals(true, false, "Fehlerhaft");
    equals("Wert", "Wert", "Erfolgreich");
});


Wem jetzt das expect aufgefallen ist, es ist die Anzahl der zu erwartenden Assertions.

Um Objekte zu vergleichen wird die „same“ Funktion angeboten. Hierfür ein einfaches Objekt „User“ mit der Property „Name“

test("Der 'same' Test", function () {
    expect(2);
    var user1 = new User("Name1");
    var user2 = new User("Name2");
    ok(true, "User wurden erstellt");
    same(user1, user2, "user1 und user2 sind identisch");
});

 
Der Test sollte nun eine erfolgreiche und eine fehlgeschlagene Assertion ausgeben, da die User zwar erfolgreich erstellt wurden, aber nicht den gleichen Namen haben.

Zudem bietet QUnit einige Callbacks die überschrieben werden können, wie bspw. log um die Fehler zu loggen, testStart, testDone…

QUnit.log = function(result, message) {
    alert(message);
}


Ein weiterer Punkt bei QUnit ist die Unterteilung in Modulen. Der Aufruf erfolgt über module( name, [lifecycle] ), wobei lifecycle die setup und teardown callbacks darstellt, die bei jedem Test im aktuellen Modul ausgeführt werden. Dies kann verwendet werden, um bspw. bestimmte Test- Daten für jeden Test zu erstellen.

module("Modul abc", {
    setup: function () {
        this.user = new User("TestUser");
    },
    teardown: function () {
        this.user = null;
    }
});


test("User Test", function () {
    expect(1);
    equals(this.user.name, "TestUser");
});


Nun ist Kreativität gefragt! Wie JQuery das macht sieht man bei den Beispielen in der QUnit Dokumentation im unteren Bereich der Seite.

Anbei gibt’s wie immer ein Beispiel dazu!
Viel Spass beim Testen :-)

Eingetragen von Roberto | 2 Kommentare
Abgelegt unter: , , ,

Attachment(s): QUnitExample.zip

Dynamic Objects in C# 4.0

Eine neues Feature in C# 4 ist die „Dynamic Language Runtime“ (DLR). Mit dem „dynamic“ Schlüsselwort ist es möglich zur Laufzeit Properties und Methoden an Objekten zu binden. Dabei gibt es allerdings keinerlei Überprüfung ob diese auch existieren – dafür erhöht sich die Flexibilität der Anwendung.
Eigene Klassen können durch die Implementierung des DynamicObject und durch die Überschreibung der gewünschten Methoden erstellt werden.

Ein praktisches Beispiel dazu:
Wer kennt das Problem nicht, es gibt eine Javascript Komponente die im Header der Seite einiger Einstellungen bedarf.  Diese möchte man aber nicht per Javascript, sondern per Asp.Net setzen. Ist ja kein Problem, aber jede einzelne Property Serverseitig abzubilden kostet viel Zeit, insbesondere wenn die Komponente ständig weiterentwickelt wird.

Das Ausgabe sollte in JavaScript etwa so aussehen:

var myObject = new JavaScriptComponent();
myObject.width = 400;
myObject.show = false;


Dynamic to the rescue!
Wie oben beschreiben ist es möglich an dynamischen Objekten beliebige Properties und Methoden zu binden.
Dafür einfach eine Klasse ClientScriptCreator die von DynamicObject ableitet.

public class ClientScriptCreator : DynamicObject

Um die dynamischen Properties zu speichern wird ein Dictionary verwendet

Dictionary<string, object> _members;

Für die Benennung der clientseitigen Klasse und des Objekts werden zwei weitere Member deklariert

string _clientClassName;
string _clientObjectName;


Die drei Member werden nun im Konstruktor der Klasse übergeben/gesetzt

public ClientScriptCreator(string clientClassName, string clientObjectName)
{
    _members = new Dictionary<string,object>();
    _clientClassName = clientClassName;
    _clientObjectName = clientObjectName;
}


Mit der Überschreibung der Methode TrySetMember werden die Properties (falls noch nicht vorhanden) in das Dictionary gespeichert.
Diese liefert einen SetMemberBinder und ein Objekt mit dem Wert.

public override bool TrySetMember(SetMemberBinder binder, object value)
{
    if (!_members.ContainsKey(binder.Name))
    {
        _members.Add(binder.Name, value);
        return true;
    }
    else
    {
        return false;
    }
}


Ähnlich dazu gibt es die TryGetMember Methode, welche den gesetzten Wert (falls vorhanden) zurückgibt

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    if (_members.ContainsKey(binder.Name))
    {
        result = _members[binder.Name];
        return true;
    }
    result = null;
    return false;
}


Nun ist die Klasse im Stande dynamisch Properties zu setzen und zurück zu geben.

dynamic clientScriptCreator = new ClientScriptCreator();
clientScriptCreator.PropertyOne = "'Some string'";
clientScriptCreator.PropertyTwo = "false";
clientScriptCreator.PropertyThree = "4711";


Das client Script muss nun zusammengesetzt werden um in den Header geschrieben zu werden.
Dafür eine Methode GetDefaultScript die den Dictionary Member durchläuft und diesen in ein <script type=“test/javascript“> Konstrukt wiedergibt.

private string GetDefaultScript()
{
    StringBuilder sb = new StringBuilder();
    sb.Append("<script type=\"text/javascript\">" + Environment.NewLine);
    sb.Append(_clientObjectName + " = new " + _clientClassName + "();" + Environment.NewLine);
    foreach (KeyValuePair<string, object> kvp in _members)
    {
        sb.Append(_clientObjectName + "." + kvp.Key + " = " + kvp.Value + ";" + Environment.NewLine);
    }
    sb.Append("</script>");
    return sb.ToString();
}


Das Script soll anschließend in einem Literal Control im head Bereich der Seite stehen

<head>
    </asp:Literal ID="ClientScriptLiteral" runat="server">
</head>


Was noch fehlt ist der Aufruf der Methode, die das client Script zurückgibt.
Dafür die TryInvokeMember überschreiben, damit dynamische Methoden aufgerufen werden können. In dem folgenden Abschnitt wird auf die Methode „ToDefaultScript“ überprüft – wenn diese vorkommt, wird das Script zurückgeliefert:

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
    if (binder.Name == "ToDefaultScript")
    {
        result = GetDefaultScript();
        return true;
    }
    result = null;
    return false;
}


In der Page_Load der Seite erfolgt der Aufruf wie folgt

ClientScriptLiteral.Text = clientScriptCreator.ToDefaultScript();

Das Ganze ist nun wirklich dynamisch. Wird die JavaScript Komponente weiter entwickelt, braucht man sich nicht mehr darum zu kümmern und kann die Objekte wo gewünscht einfach erweitern.

Anbei gibt’s noch das Beispiel als Visual Studio 2010 Projekt dazu.
Eingetragen von Roberto | 1 Kommentare
Abgelegt unter: , ,

Attachment(s): ClientScriptCreator.zip

ASP.NET Update Panel, Callback und jQuery Webservice - Drei Ajax Techniken im Überblick

Dass mit diversen Techniken clientseitig über Javascript serverseitige Funktionen aufgerufen werden können ist nichts Neues.
Doch welche Technik überzeugt und welche passt am besten? In diesem Artikel werden drei grundlegende Vorgehensweisen erläutert und miteinander verglichen: Das Updatepanel, das Callback und der clientseitige Webservice Aufruf. Dazu gibt es jeweils ein kurzes Beispiel.

Das Updatepanel – Segen oder Fluch?

Immer wieder wird über folgende Wörter gestolpert: „böses Asp.Net Updatepanel…“. Doch warum wird es dann oft verwendet und vor allem: warum ist es böse?
Grundsätzlich entspricht das Updatepanel dem traditionellen Postbackmodell – eine Anforderung ist immer noch ein vollständiges Postback. Das Formular wird inklusive Viewstate an den Server gesendet. Der Großteil des Lebenszyklus der Seite bleibt erhalten und das Rendern bleibt dem Server überlassen.

„Es ist aber immer noch schneller als ein komplettes Postback!“

Das stimmt tatsächlich - der Unterschied und Vorteil liegt also nicht in der Anforderung, sondern in der Antwort des Servers.
Nur bestimmte Teile der Seite bzw. des Viewstates werden zurückgegeben und ausgetauscht (Partielles Rendering).

Update Panel
Fazit:

Diese Variante ist mit Sicherheit die einfachste. Dadurch, dass sich der auszutauschende Inhalt innerhalb eines Containers befindet, kann das UpdatePanel als relativ elegant bezeichnen werden. Allerdings leidet die Performance unter der Datenmenge die von Client zu Server gesendet wird (gesamte Formulardaten inkl. Viewstate). Das UpdatePanel ist nicht clientseitig beeinflussbar und somit auf Serverseitige Steuerung angewiesen. Die Features leiden darunter.

Einfach
Elegant
Performant

Beispiel:

Um die partiellen UpdatePanel Aktualisierungen nutzen zu können muss der Seite ein ScriptManager hinzugefügt werden

<asp:ScriptManager ID="ScriptManager1" runat="server"/>

Die Deklaration des UpdatePanels siehst dann wie folgt aus (mehr Infos gibt es hier)

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
        <asp:Label ID="Message" Text="This is a update panel…" runat="server" />
        <asp:Button ID="Save" Text="Save" runat="server" />
    </ContentTemplate>
</asp:UpdatePanel>


Das Callback – Keine Angst, es beißt nicht!

Das Callback ruft Servercode mittels Clientscript auf, ohne dadurch(zwingend) einen Postback zu verursachen. Klingt auf Anhieb super – aber ohne Postback gibt’s auch keine Formular Daten, d.h. die Anforderung besteht aus Parametern die per Javascript manuell angegeben werden.
Wie das Updatepanel nützt auch das Callback das XmlHttp Objekt intern zum Aufruf der Serverseitigen Methoden.
Die gesendeten Daten sind sehr gering. Allerdings wird der Lebenszyklus der Seite normal durchlaufen. Das kann sich bei einer größeren Seite durchaus bemerkbar machen, denn sie wird wie deren Controls und Members normal erstellt.
Rückgabe des Callbacks können per json Serialisierte Daten oder auch normaler HTML Text sein.

Callback

Fazit:

In bestimmten Fällen kann es durchaus Sinn machen, Servercode aufzurufen ohne dadurch ein Postback zu verursachen oder Teile der Seite neu zu rendern.  Der Datentransfer ist sehr gering – allerdings wird der Lebenszyklus der Seite trotzdem durchlaufen. Der Einbau ist durch die Implementierung zweier Interfaces relativ einfach und sauber.

Einfach
Elegant
Performant

Beispiel:

Damit das Callback funktioniert müssen folgende Interfaces implementiert werden:
  • ICallback
  • ICallbackEventHandler
Diese können sowohl in ein Control, als auch in eine normale Seite implementiert werden.
Das Interface hat zwei Methoden:

RaiseCallbackEvent wird für den clientseitigen Aufruf über eine Javascript Funktion benötigt

public void RaiseCallbackEvent(string eventArgument)
{
  //do something
}


GetCallbackResult wird aufgerufen, wenn RaiseCallbackEvent durch ist. Die Rückgabe würd asynchrony an die Javascript Funktion zurück gegeben.

public string GetCallbackResult()
{
  return "something";
}


Im Load oder Init Event werden folgende clientseitige Methoden registriert:

  • CallServer(arg, context) – ruft die serverseitige Methode auf
  • ReceiveServerData(arg, context) – bekommt das Ergebnis durch den „arg“ Parameter von GetCallbackResult()
protected void Page_Load(object sender, EventArgs e)
{
    ClientScriptManager scriptManager = Page.ClientScript;
    String callbackReference = scriptManager.GetCallbackEventReference(this, "arg",
    "ReceiveServerData", "");
    String callbackScript = "function CallServer(arg, context) {" + callbackReference + "; }";       
    scriptManager.RegisterClientScriptBlock(this.GetType(),"CallServer", callbackScript, true);
}


Die 2 Javascript Funktionen sehen wie folgt aus

function ReceiveServerData(arg, context)
{
  alert(arg);
}
function CallSrv()
{
  CallServer('get something', '');
}


Scriptservice – jQuery sei Dank!

Durch jQuery wurde es relativ einfach einen Webservice über Javascript aufzurufen. Wie bereits in einem letzten Eintrag beschrieben, werden über einen minimalen HTTP Post Request an den Server die benötigten Daten zurückgesendet. Diese sind in der Regel um einiges geringer als der komplette HTML Code dazu. Diese „reine Ajax Variante“ bringt das Problem mit sich, dass das Rendering auf dem Client stattfindet und dafür gibt es nur zwei simple Funktionen:
  • innerHTML, das den kompletten Inhalt eines Elements austauscht
  • DOM APIs, die mit Tags und Attributen arbeiten

Webservice

Fazit:

In der Praxis ist eine reine Ajax Vorgehensweise meist besser als das UpdatePanel Prinzip. Das Rendering findet auf dem Client statt und der Server schickt nur die benötigten Daten zurück. Dies ist zwar performant, dafür aber aufwendiger zu integrieren. Wer mit jQuery arbeitet, sollte sich auf jeden Fall über diese Möglichkeit Gedanken machen.

Einfach
Elegant
Performant

Beispiel:

Ein kurzer Auszug aus einem Beispiel. (In voller Länge)

Die Webservice methode

[WebMethod]
public string GetTestValue()
{
    return "Test value";
}


Der Javascript Aufruf

$.ajax({
    type        : "POST",
    url         : "Webservices.asmx/GetTestValue",
    data        : "",
    contentType : "application/json; charset=utf-8",
    dataType    : "json",
    success     : function( Result ) {
        // Result.d enthält die Rückgabe
        $("#MyContainer").html(Result.d);
    }
}


Welche Technik nun die passende ist, hängt immer stark vom Anwendungsfall ab - jede Technik hat ihre Stärken und Schwächen.
Grundsätzlich gilt - wenn Post Daten, Control States o.Ä. benötigt werden (bspw. bei einem größeren Eingabe-Formular), kann das UpdatePanel verwenden werden.
Wird darauf verzichtet, sollte das Callback bzw. der Webservice Aufruf in Betracht gezogen werden!

Update:
Einen Vergleich zu Ajax.Pro von René Drescher-Hackel findet sich hier!
Eingetragen von Roberto | 5 Kommentare

jQuery Plugin selbstgemacht - Dynamisches laden von ASP.NET Controls

Wer kennt sie nicht... die zauberhaften ajax Kekse.

Doch was steckt dahinter?

In diesem Beitrag werde ich anhand eines Beispiels erklären, wie man ein jQuery Plugin dafür erstellen kann.

Die Vorgehensweiße:
Über einen clientseitigen Funktionsaufruf werden wir den Inhalt eines HTML Containers mit dem eines klassischen ASP.NET Controls ersetzen.
Das alles passiert mit einem Webservice Aufruf per jQuery der den HTML Code zurückliefert.

Schritt 1: Das Plugin

Eine kurze Einführung in jQuery Plugins.
In jQuery werden Objekte mit $.fn.<Name> = function() {} erweitert.
Allerdings sollte der "$" Alias vermieden werden, da Konflikte mit anderen Javascript Bibliotheken(bspw. prototype.js) entstehen können.
Die am häufigsten benutzte Vorgehensweise sieht so aus:

(function($){
    // --- Plugin kommt hier
})(jQuery);


Das neue Plugin nennt sich ascxLoder. Als Parameter die Pfadangabe und eine Sammlung mit Einstellungen.
Natürlich werden auch Standard Werte benötigt, damit ewige Parameter Übergaben vermieden werden.

$.fn.ascxLoader = function( path, params ) {
    var defaults = {
        showLoadingScreen = true
    };
    var config = $.extend(defaults, params);
    // --- Mit $(this) können wir auf den Container zugreifen.
    // --- über config.<Value> die gewünschte Einstellung.
}


Der Aufruf erfolgt dann über:

$("#MyContainer").ascxLoader("~/Control.ascx", { showLoadingScreen: false });

Schritt 2: Der Webservice Aufruf

Damit das Plugin jetzt auch den gewünschten Effekt bringt, bauen wir einen einfachen Webservice Request ein.

(function($){
$.fn.ascxLoader = function( path, params ) {
    var defaults = {
        path: "Control.ascx",
        showLoadingScreen: true
    };   
    var config = $.extend(defaults, params);
    var obj = $(this);
    path = path || config.path;
    // --- der Webservice Aufruf mit Pfad übergabe:
    $.ajax({
        type        : "POST",
        url         : "Webservices.asmx/GetControl",
        data        : '{"Path":"' + path + '"}',
        contentType : "application/json; charset=utf-8",
        dataType    : "json",
        success     : function( result ) {
            // result.d enthält den HTML code für unseren Container
            obj.html( result.d );
        }
    });
}
})(jQuery);


Hinweis: "path = path || config.path" bedeutet: falls path undefined ist, nimm config.path

Schritt 3: Der Webservice

Nun zum Webservice, der uns das gewünschte Control als HTML code zurückgibt.

<WebMethod()> _
Public Function GetControl( Path As String ) As String
Dim page        As New Page()
Dim userControl As UserControl = Page.LoadControl( Path )
Dim output      As New System.IO.StringWriter()
    page.Controls.Add( userControl )
    HttpContext.Current.Server.Execute( page, output, false )      
    return output.ToString()
End Function


Über folgenden Funktionsaufruf lässt sich das Control neu laden.

   $("Container").ascxLoader("~/Controls/Control.ascx");

War doch gar nicht so schwierig oder?

Anbei gibt es ein Beispiel zum selber ausprobieren.
Dieses beinhaltet neben dem eben gezeigten Beispiel auch die Möglichkeit, einer Anforderungen Parameter mitzugeben und so je nach Aufruf ein geändertes Control zurückzugeben.

Update:
Eine gelungene und erweiterte Version des Plugins, von Kristof Zerbe findet sich hier!



Eingetragen von Roberto | 5 Kommentare
Attachment(s): DemoJQueryPlugin.zip

Übergabe komplexer Objekte per Json Serialisierung von jQuery zu ASP.NET Webservice

Die Übergabe komplexer Objekte an einen Webservice per Json Serialisierung ist grundsätzlich relativ einfach, hin und wieder jedoch etwas tricky.

Warum jQuery und warum Json?
Ich bin ein großer Fan von jQuery. Der Webservice Aufruf erfordert keine Einbindung eines ScriptManagers und ist in weniger als 10 Zeilen Code geschrieben.
Die Datenmenge durch Json Serialisierung ist sehr gering. Bspw. könnte ein Grid mit 1000 Datensätzen etwa 200KB an Daten enthalten.
Json Serialisierte Webservice Methoden erfordern zwingend:
  • Einen HTTP Post request
  • Den Header: "Content-Type: application/json; charset=utf-8"
dies hat allerdings den Sicherheits- Vorteil, dass die Methode nicht über <script src="http://www.domain.de/Webservice.asmx/Methode"> ansprechbar ist, da die Browser das einfügen von eigenen Headern bei einem script Include nicht erlauben. Mehr zum Thema in einem ausführlichen Beitrag von Scott Guthrie.

Los gehts!
Was wird benötigt?
Damit der Webservice per jQuery ansprechbar ist, reicht es, folgendes Attribut hinzuzufügen:

<System.Web.Script.Services.ScriptService()> _

Die Einbindung des Webservices über Scriptmanager wird nicht benötigt.

Zum Empfang von Daten liefert eine Webservice Methode bspw. eine List( Of <T> ) zurück.

<WebMethod()> _
Public Function GetCustomers() As List( Of Customers )
    Return Customers
End Function


Diese Methode wird per jQuery angesprochen

$.ajax({
    type        : "POST",
    url         : "Webservices.asmx/GetCustomers",
    data        : "",
    contentType : "application/json; charset=utf-8",
    dataType    : "json",
    success     : function( Result ) {
        // Result.d enthält die Customers liste
        $.each( Result.d, function( Key, Value ) {
            alert( Value.Name );
            // ...
        }
    }
}


Zum senden der Daten

<WebMethod()> _
Public Function SetCustomer( ByVal Customer As Customer ) As Boolean
    Dim CustomerName As String = Customer.Name
    ' ....
    Return True
End Function


In Javascript kann man mit Json2 Objekte einfach über JSON.Stringify( <Objekt> ) serialisieren.

Customer = function() {
   this.Name;
}
var CurrentCustomer = new Customer();
    CurrentCustomer.Name = "TestName";

$.ajax({
    type        : "POST",
    url         : "Webservices.asmx/SetCustomer",
    data        : '{"Customer":' + JSON.stringify( CurrentCustomer ) + '}',
    contentType : "application/json; charset=utf-8",
    dataType    : "json",
    success     : function( Result ) {
        // Result.d enthält eine boolschen Wert
        if( Result.d ) {
            alert( "Daten erfolgreich gespeichert." );
        }
    }
}


Anbei gibts noch ein ausführlicheres Beispiel dazu.

Eingetragen von Roberto | 5 Kommentare
Attachment(s): DemojQueryWebservice.zip

Besser spät als nie!

Jeder Anfang ist schwer :-) Aber irgendwann muss man ihn machen!

Dieser Blog wird Themen rund um .Net, Ajax und Javascript beinhalten.

Ein Dankeschön geht an:
  • Peter Bucher für die über Monate gebrachte Überzeugungskraft einen Blog zu starten.
  • Stefan Falz für das Hosting des Blogs und der Möglichkeit einen Blog bei ASP.NET Zone zu bekommen!
  • Jeden der diesen Beitrag liest und vielleicht in Zukunft mal vorbeischaut :)
Also auf gutes Gelingen!
Eingetragen von Roberto | 1 Kommentare