.
Anmeldung | Registrieren | Hilfe

.NET-Blogs Archiv Juni 2008

P/Invoke Interop Assistant

29.06.2008 15:34:00 | Klaus Bock

Wer des öfteren Gebrauch von P/Invoke macht hat sich bestimmt schon das ein oder andere mal ein Werkzeug gewünscht, welches die diversen Typen, Konstanten und Prozeduren der win32-API zeigt. Bislang gab es eigentlich nur die Seite PInvoke.NET sowie deren Visual Studio Add-In. Da es aber genug Leute gibt die nur die Visual Studio Express Editions verwenden, welche ja bekanntlich keine Add-In's erlauben, blieb bis Dato nur die Seite PInvoke.NET oder eben die mühsame Suche im Internet.

Mit dem P/Invoke Interop Assistant hat nun Microsoft ein genau solches Werkzeug auf CodePlex zur Verfügung gestellt. Der Toolkit beinhaltet die Konsolenanwendungen sigimp.exe zur Konvertierung von nicht verwalteten in verwalteten Code und sigexp.exe zur Konvertierung von verwalteten in nicht verwalteten C Code. Die interessanteste Anwendung dürfte jedoch die GUI-Anwendung winsiggen.exe sein. Sie zeigt im Reiter SigImpSearch die verschiedenen win32-API Prozeduren, Typen und Strukturen in einer Tabelle an. In einer DDL kann die Auswahl auf einen bestimmten Typ, z.B.: Prozeduren, eingeschränkt werden. Wer den Namen der Prozedur kennt, kann in der Textbox Name den Namen eingeben. Während der Eingabe wird die Auswahl in der Tabelle bereits verfeinert. Jetzt braucht nur noch die benötigte Prozedur ausgewählt zu werden und mit einem Klick auf die Schaltfläche Generate wird im rechten Code-Fenster eine komplette Klasse NativeMethods sowie evtl. benötigte Strukturen in der ausgewählten Sprache erzeugt. Per copy paste braucht der erzeugte Code nun nur noch in eine Klassen-Datei eingefügt zu werden und die Arbeit ist getan. Beim erzeugen des verwalteten Code werden auch gleich die passenden InAttribute und OutAttribute gesetzt. Das Marshalling in den entsprechenden unmanaged Typ wird ebenfalls vom P/Invoke Interop Assistant übernommen.

PInvoke Interop Assistant

Einfacher geht es wirklich nicht, oder?

Die beiden Entwickler des P/Invove Interop Assistant, Yi Zhang und Xiaoying Guo, haben im MSDN Magazin einen sehr guten Artikel zum Marshalling zwischen verwaltetem und nicht verwaltetem Code veröffentlicht. Dieser Artikel zeigt auf verständliche Art die Grundlagen des Marshalling und stellt somit einiges an Hintergrundinformationen zum P/Invoke Interop Assistant zur Verfügung.

Was ich mir in der Anwendung noch wünschen würde ist zum einen: eine Verknüpfung zur Dokumentation der jeweiligen Funktion in der MSDN; und zum anderen eine einfache Möglichkeit die vorhandene windows.xml, die als Datenbasis für die win32-API verwendet wird, um eigene oder nicht beachtete Einträge zu erweitern.

 

Technorati-Tags: |

Eine Liste von Strings (Oder andere Objekte ohne Eigenschaften) an ein Control binden

29.06.2008 04:56:00 | Peter Bucher

Objekte bspw. an einen Repeater zu binden ist relativ einfach, die Eval()-Methode benötigt einen String-Parameter der die Eigenschaft des Objektes darstellt.
Eine Liste (oder Array) von Strings hat jedoch keine schlaue Eigenschaft, <String>.Length bringt auch nicht das gewünschte.

Es funktioniert aber mit einem simplen Kommando:


List<string> list = new List<string>();
list.Add("Lorem");
list.Add("Ipsum");
rptTest.DataSource = list;
rptTest.DataBind();

(ASPX Code im Repeater ItemTemplate)


<ItemTemplate>
    <%# Container.DataItem %><br />
</ItemTemplate>

Update:
Das ganze funktioniert natürlich mit allen Objekten, die keine Eigenschaften haben.
Bspw. mit einem Objekt-Array:



object[] arr = new object[] {
    "Lorem",
    "Impsum",
    "Dorem"
};

rptTest.DataSource = arr;
rptTest.DataBind();

Das ist alles, somit wird der aktuelle String in der Liste dargestellt.

Support aus einer etwas anderen Richtung :-)

26.06.2008 21:27:50 | Oliver Scheer

Deutsche Silverlight Webcasts verfügbar

26.06.2008 21:18:40 | Oliver Scheer

Meine Kollegen Carsten Humm und Philipp Bauknecht und meiner einer. Haben eine Reihe von Silverlight Webcasts fertig gestellt.

Die Webcasts sind hier zu finden:

http://www.microsoft.com/germany/msdn/webcasts/serien/MSDNWCS-0806-03.mspx

http://www.microsoft.com/germany/msdn/webcasts/serien/MSDNWCS-0806-01.mspx

Viel Spaß.

Pfade - relativ / absolut / Root Operator, etc...

26.06.2008 17:59:00 | Peter Bucher

Gerade bei Neueinsteigern in der Webentwicklung gibt es oft Verwirrung bei den Pfadangaben.
Die folgenden Links sollen bei Begriffen wie: Root Operator, relativ, absolut, etc... ein wenig Klarheit schaffen.

[MSDN: ASP.NET-Webseitenpfade]

[SelfHtml: Referenzieren in Html]

[SelfHtml: Basis (base-Tag)]

Buchempfehlung: Test-Driven-Development. By Example.

24.06.2008 20:39:55 | Robert Mühsig

Wer einen netten Einstieg in die “TDD-Welt” wagen möchte, dem empfehle ich das Buch “Test-Driven-Development. By Example” von Kent Beck.

In diesem Buch wird an 2 Beispielen TDD “in der Praxis” gezeigt.
Das erste Beispiel ist in Java geschrieben, dabei dreht es sich um ein Finanzbeispiel um verschiedene Währungen miteinander zu addieren etc.
Im Laufe des Beispieles werden verschiedene Techniken gezeigt und der Code entsteht nach und nach.
Im zweiten Beispiel wird mit Phyton das xUnit Beispiel gezeigt. Dabei geht es darum, ein eigenes Test-Framework zu erstellen.

Die “zweite” Hälfte des Buches ist dann mit Patterns, Vorgehensweisen etc. gefüllt - sodass man ein guten Einstieg erlebt.
Mir hat bei dem Beispielen manchmal etwas der Überblick gefehlt, zudem ist das englisch an machen stellen etwas anspruchsvoller (allerdings ist es eine recht lockere Schreibweise und angenehm zu lesen :) ).

Fazit: Empfehlenswert - und für 20 Euro auch nicht zu teuer :)

Amazon.de Widgets

ShareThis

HowTo: Membership in Klassenbibliotheken / DLLs

23.06.2008 23:09:36 | Robert Mühsig

In diesem HowTo habe ich das ASP.NET Membership System mal kurz vorstellt. Ich hab in einem Projekt nun das Membership System eingesetzt und auch seine großen Schattenseiten kennengelernt.

Der erste große Kritikpunkt:
Da entwirft man eine 3-Tier Architektur und am Ende hängt einer der wichtigsten Teile (das Usersystem) mit in der WebApp - das ist mehr als unschön.

Mein Wunsch: Das Usersystem soll mit im Backend verschwinden.
Eine grobe Skizze (wobei man den “Service” noch in andere Schichten unterteilen kann):

image
Die “App.Config” sollte den ConnectionString speichern und auch die Membership-Konfiguration übernehmen.
Ich sage hier bewusst “sollte”, da ich es leider nicht ganz so perfekt hinbekommen hab.

Allerdings erst mal Schritt für Schritt: Das Membership-System muss in eine DLL rein.

Grundsätzlicher Aufbau:

image

  • DllMembership.Lib” ist unser Service in dem wir unseren “MembershipService” haben.
  • DllMembership.Web” ist eine gewöhnliche ASP.NET Website.
  • DllMembership.Test” ist unser UnitTest-Projekt

Schritt 1: Membership-System in der DLL Konfigurieren

Als erstes müssen wir in der App.Config folgende Konfiguration erstellen:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <add name="ASPNETDBConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename='C:\Users\rmu\Documents\Visual Studio 2008\Projects\Blogposts\DllMembership\DllMembership.Lib\DB\ASPNETDB.MDF';Integrated Security=True;User Instance=True"
     providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.web>
    <membership>
      <providers>
        <remove name="AspNetSqlMembershipProvider"/>
        <add name="AspNetSqlMembershipProvider"
             type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
             connectionStringName="ASPNETDBConnectionString"
             enablePasswordRetrieval="false"
             enablePasswordReset="true"
             requiresQuestionAndAnswer="false"
             applicationName="/"
             requiresUniqueEmail="true"
             passwordFormat="Hashed"
             maxInvalidPasswordAttempts="12"
             minRequiredPasswordLength="1"
             minRequiredNonalphanumericCharacters="0"
             passwordAttemptWindow="10"
             passwordStrengthRegularExpression="" />
      </providers>
    </membership>
  </system.web>
</configuration>

Die Data Source muss natürlich entsprechend des DB Speicherortes ausgewechselt werden.
Wichtiger Hinweise: Ich verwende dieselbe DB wie aus dem anderen Blogpost.
Ganz wichtig: Damit die App.Config auch angenommen wird, müssen die Properties richtig gesetzt sein:

image

Jetzt fügen wir noch die “System.Web” Referenz hinzu:

image

Schritt 2: Service schreiben

Jetzt implementieren wir unseren sehr einfachen Service:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;

namespace DllMembership.Lib
{
    public class MembershipService
    {
        public IList<User> GetUsers()
        {
            MembershipUserCollection col = Membership.GetAllUsers();
            List<User> returnList = new List<User>();
            foreach (MembershipUser user in col)
            {
                User u = new User();
                u.Name = user.UserName;
                u.Email = user.Email;
                returnList.Add(u);
            }
            return returnList;
        }

        public User Login(string username, string password)
        {
            User returnUser = new User();

            if (Membership.ValidateUser(username, password))
            {
                returnUser.IsLoggedIn = true;
                returnUser.Name = username;
                returnUser.Password = password;
                return returnUser;
            }
            else
            {
                returnUser.IsLoggedIn = false;
                return returnUser;
            }
        }

        public User GetUser(string username)
        {
            MembershipUser user = Membership.GetUser(username);
            User returnUser = new User();
            returnUser.Name = user.UserName;
            returnUser.Email = user.Email;
            return returnUser;
        }
    }
}

Dieser Service gibt ein “User” Objekt zurück (im Prinzip findet ein Mapping zwischen dem MembershipUser und unserem User statt) :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DllMembership.Lib
{
    public class User
    {
        public bool IsLoggedIn { get; set; }
        public string Name { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }
}

Diese “User” Klasse fungiert ebenfalls als “Result” für unsere Service-Aufrufe. Klappt zum Beispiel die Login-Methode nicht, wird einfach die “IsLoggedIn” Property auf false gesetzt.

Schritt 2.5: Unit Tests

Ich habe 3 Unit-Test Methoden geschrieben, welche die grobe Funktionalität testen. Das klappt soweit.

Schritt 3: Das Web-Projekt
Damit unser Nutzer während einer Session auch eingeloggt bleibt, müssen wir noch die Form-Authentication aktivieren.
Hier müssen wir noch den Cookie “manuell” setzen. Dazu habe ich eine AppHelper mir erstellt:

namespace DllMembership.Web
{
    public static class AppUtil
    {
        public static User GetActiveUser()
        {
        if(HttpContext.Current.User.Identity.IsAuthenticated == false)
            {
                return new User() { IsLoggedIn = false };
            }
            else
            {
                MembershipService service = new MembershipService();
                User returnValue = service.GetUser(HttpContext.Current.User.Identity.Name);
                returnValue.IsLoggedIn = true;
                return returnValue;
            }
        }

        public static User Login(string username, string password)
        {
            MembershipService service = new MembershipService();
            User returnValue = service.Login(username, password);
            if (returnValue.IsLoggedIn)
            {
                FormsAuthentication.SetAuthCookie(returnValue.Name, true);
                returnValue.IsLoggedIn = true;
            }
            else
            {
                returnValue.IsLoggedIn = false;
            }

            return returnValue;
        }
    }
}

Hierbei gibt es zwei Methoden “GetActiveUser” und “Login”.
Zum “Login”:
Diese Methode übergibt die Parameter zum Service und wenn der Login erfolgreich war, wird dieser über ein Cookie über die Session hinweg authentifiziert.
Die “GetActiveUser”:
Diese Methode schaut einfach, ob der User im HttpContext authentifiziert ist, wenn nicht, gibt es keinen angemeldeten Nutzer, ansonsten wird der aktuelle Nutzer geladen.

Schritt 4: Ausgabe des Usernamen auf der Website

            DllMembership.Lib.User returnUser = AppUtil.GetActiveUser();
            if (returnUser.IsLoggedIn == false)
            {
                this.UserName.Text = "unangemeldet";
            }
            else
            {
                this.UserName.Text = returnUser.Name;
            }

Dadurch können wir leicht prüfen, ob jemand angemeldet ist, oder nicht.

Das Problem dabei
Leider geht die Lösung so wie ich sie hier gepostet habe, nicht ganz, denn man muss leider in der Web.Config die Membership Konfiguration und den ConnectionString noch extra angeben.
Das “Witzige” an der Geschichte: Die Unit-Tests laufen. Sobald dies aber auf der Webseite genutzt wird, überschreiben wohl die Web.Config Einstellungen die App.Config Einstellung - ihr könnt es gerne selber ausprobieren.
Ich finde das etwas unschön, aber verschmerzbar (bzw. fällt mir nix anderes ein).
Wenn jemand eine Lösung weiß, dann her damit :)

Fazit
Das ist nur ein “Prototyp”, ich habe längst nicht alles fertig mir ausgedacht und würde vielleicht noch extra Properties einbauen und den Service umbasteln. Allerdings sollte dies der erste Schritt sein um zu zeigen, wie man das Membership dahin packt, wo es hingehört: In eine andere Schicht.

[ Download Source Code ]
* In den *.config muss der ConnectionString angepasst werden
** Anmeldedaten stehen in der ReadMe.txt in WebApp Ordner

ShareThis

Was hat eigentlich das "DirectX" im Hintergrund verloren?

23.06.2008 20:51:00 | Peter Bucher

In der Hintergrundgrafik meines Blogs findet sich schon lange auch "DirectX".
Wie wahrscheinlich die meisten wissen, handelt es sich hierbei um eine Grafik-API von Microsoft.

In der Heutigen Zeit wird alles auf 3D getrimmt. Mich als grosser Fan von Echtzeit-Strategiespielen wie bspw.:

Command & Conquer Serie, KKND Serie, Warcraft Serie, Starcraft, ...

haben die ersten Ausgaben dieser 3D-Produkte nicht wirklich angesprochen.
Die zweiten Ausgaben von Echtzeit-Strategiespielen waren schon um einiges besser, jedoch finde ich 2D in Isometrischer Ansicht immer noch prikelnder.

Dort ist die Übersicht immer schön gewahrt und das Spiel hat einen eigenen Style.
Da ich vorallem auch ein riesen Fan von KKND bin und dort ein dritter Teil wohl nicht mehr kommen wird,
kam mir die Idee sowas ähnliches selber zu machen.

Vor ca. 1 1/2 Jahren habe ich angefangen an einem Spiel zu schreiben.
Primär war es als Lernobjekt gedacht, um mit der Sprache firm zu werden, sekundär um Erfahrungen in Sachen Profiling, Performance, Grafik, ...
zu sammeln.

Ich war schon ziemlich weit, fokusierte mich aber aus verschiedensten Gründen wieder 100% auf ASP.NET und verlor das Projekt aus den Augen.
Nun, viel steht schon - jedoch würde ich Heute wieder von Vorne anfangen, da ich jetzt vieles einfach viel besser machen könnte.
Auf jeden Fall konnte ich so viele Erfahrungen sammeln und werde das Projekt wohl wieder einmal aufziehen.
Das Projekt habe ich mit Managed DirectX (1.1) angefangen. In der Zwischenzeit gab es MDX 2.0, das eingestellt wurde.
Jetzt steht der Fokus bei Microsoft auf XNA, das eine komplette Plattform für die 3D und Spieleentwicklung offenlegt.

So, das Rätsel um DirectX wurde gelöst. Wenn alles klappt, wird von mir zu diesem Thema in absehbarer Zeit auch hier wieder etwas zu finden sein :)

Einen SqlSiteMapProvider ohne direkte Datenbankanbindung nutzen.

22.06.2008 17:35:06 | Klaus Bock

Als ich vor ca. 2 Jahren begann mich mit einem SiteMapProvider zu befassen der als SiteMapDataSource eine SQL-Datenbank benutzt, bin ich auf diesen Artikel von Jeff Prosise gestoßen. In seiner Implementierung hatte er schon fast alle meine Anforderungen erfüllt. Er benutzt die SqlCacheDependency zum überwachen von Änderungen an der Sitemap Tabelle in der Datenbank, sowie das securityTrimmingEnabled-Konfigurationsattribut welches den Wert der Eigenschaft SecurityTrimmingEnabled des SiteMapProvider setzt und somit die Verwendung von Roles unterstützt. Was mit allerdings fehlte, war eine "Entkoppelung" des SiteMapProvider von der Datenbank. Da in den meisten ASP.NET-Anwendungen exzessiver Gebrauch von der Datenbankverbindung gemacht wird wollte ich den SqlSiteMapProvider so gestalten, dass er nicht bei jedem laden der Seite eine Verbindung zur Datenbank aufbaut. Es sollte auch eine Möglichkeit geschaffen werden den Provider über einen Webdienst mit der Datenbank kommunizieren zu lassen. Dabei sollte auch gleich die Authentifizierung mit Hilfe der, in dieser Artikelreihe beschriebenen, SoapAuthHeader-Klasse ermöglicht werden. Das Ganze sollte auch noch einfach in der web.config konfiguriert werden.

Der zuletzt angesprochene Punkt: Konfiguration in der web.config ließ sich am leichtesten umsetzen. Da die Klasse SqlSiteMapProvider von der abstrakten Klasse StaticSiteMapProvider erbt, wird die Methode Initialize überschrieben. Diese Methode wird vom Framework bei der Initialisierung des Provider aufgerufen und die gesamte, den Provider betreffende, Konfiguration in der web.config als NameValueCollection an die Initialize-Methode übergeben. Jetzt braucht man nur noch die einzelnen Konfigurationsattribute auf ihren Wert zu prüfen und auszuwerten. Um zu unterscheiden ob eine direkte Datenbankverbindung genutzt wird, habe ich das Konfigurationsattribut directConnection hinzugefügt. Dieses Attribut wird in der überschriebenen Initialize-Methode in einen boolschen Wert gecastet und ausgewertet. Der Konfigurationsbereich in der web.config könnte so aussehen:

<configuration>
    <siteMap defaultProvider="SqlSiteMapProvider" enabled="true">
        <providers>
            <clear/>
            <add name="SqlSiteMapProvider" type="TestSpace.Web.SqlSiteMapProvider"
                securityTrimmingEnabled="true" secureQueryStringEnabled="true"
                directConnection="false" cacheKey="sitemapDataSet"/>
        </providers>
    </siteMap>
</configuration>

In der Initialize-Methode wird geprüft ob das Attribut in der web.config angegeben ist und dann in eine Variable vom Typ bool gecastet.

// prüfen ob directConnection in der web.config angegeben ist
if (string.IsNullOrEmpty(config["directConnection"]))
{
    config.Remove("directConnection");
    config.Add("directConnection", "true");
}

bool.TryParse(config["directConnection"], out isDirectConnection);
config.Remove("directConnection");

Die Variable isDirectConnection wird nun an mehreren Stellen in einem if-Statement verwendet um auf die vorliegende Konfiguration zu reagieren. Falls isDirctConnection true zurückgibt, wird, wie im Original Code, ein SqlDataReader verwendet um die Sitemap-Struktur zu erstellen. Wird false zurückgegeben, liegt im HttpRuntime.Cache ein DataSet vor. In diesem Fall wird ein DataTableReader verwendet um die Sitemap-Struktur zu erstellen. Beide Szenerien lassen sich ohne größeren Aufwand nutzen, da das Argument reader der beiden Methoden CreateSiteMapNodeFromDataReader(reader) und GetParentNodeFromDataReader(reader) vom Typ DbDataReader ist und somit sowohl einen SqlDataReader als auch einen DataTableReader akzeptiert. Insofern sind die Änderungen an der SqlSiteMapProvider-Klasse abgeschlossen.

Um das benötigte DataSet im HttpRuntime.Cache zu verwalten kann natürlich in jedem Anwendungsfall des SqlSiteMapProvider ein eigenes Datahandling geschrieben werden. Doch jedes einzelne Mal mich selbst um die Daten im Cache zu kümmern erschien mir zu mühsam. Also habe ich nach einem Weg gesucht um die benötigten Daten nur einmal, beim Start der Anwendung, zur Verfügung zu stellen. Es war klar, dass hier nur die Methode Application_Start der Global-Klasse in Betracht kam. Jetzt musste noch eine Klasse her, welche sich einfach in der Global-Klasse initialisieren lies und sich um die Cache-Verwaltung kümmerte. Um die Zugehörigkeit zum SqlSiteMapProvider zu zeigen, habe ich diese Klasse SqlSiteMapCacheHandler genannt. Da ich mir zur Aufgabe gemacht hatte die Daten von einem Webdienst liefern zu lassen, musste auch die Handhabung eines Webdienst-Proxy sowie die zugehörige Authentifizierung möglichst abstrakt gehalten werden. Des weiteren soll ebenfalls auf Änderungen in der zugrundeliegenden Datenbanktabelle reagiert werden. Hier habe ich mich für die Verwendung einer CacheDependency mit einem Cacheschlüssel entschieden. Dieses erschien mir am einfachsten, da Änderungen an der Sitemap-Tabelle sicherlich aus der Web-Anwendung heraus getätigt werden und somit der Cacheschlüssel einfach, im Zuge der Änderung, geändert werden kann.

Etwas komplexer verhält es sich mit der Verwendung eines Webdienst-Proxy und der Authentifizierung. Es müssen verschiedenen Objekte an die SqlCacheHandler-Klasse übergeben werden. Im einzelnen sind dies:

  1. Der eigentliche Webdienst-Proxy als Object.
  2. Die Webdienst-Proxy Methode, welche die benötigten Daten anfordert.
  3. Der für die Authentifizierung zuständige SoapHeader als Object.
  4. Die Gültigkeitsdauer des SoapHeader.

Also wurden in der SqlCacheHandler-Klasse die benötigten Eigenschaften des jeweiligen Typs angelegt.

#region Properties

/// <summary>
/// Der Name des Schlüssels unter dem das DataSet im Cache abgelegt werden soll.
/// Es muss der gleiche Name verwendet werden wie in der Konfiguration
/// des <see cref="T:TestSpace.Web.SqlSiteMapProvider"/>.
/// </summary>
/// <value>Setzt den Wert des privaten Feldes cacheKey oder gibt ihn zurück.</value>
/// <remarks>n/a</remarks>
public string CacheKey
{
    get { return this.cacheKey; }
    set { this.cacheKey = value; }
}

/// <summary>
/// Der Referenzschlüssel der überwacht werden soll. Initialisierungswert ist <c>false</c>.
/// Das bedeutet, Daten sind nicht geändert.
/// </summary>
/// <value>Setzt den Wert des privaten Feldes referenceKey oder gibt ihn zurück.</value>
/// <remarks>
/// Ein zusätzlicher Schlüssel, der für das verwendete <see cref="T:System.Web.Caching.CacheDependency"/>
/// -Objekt benötigt wird. Der Wert dieses Schlüssels, <c>true</c> oder <c>false</c>, sollte bei
/// Änderungen an der Datenbanktabelle ebenfalls auf <c>true</c>, als Daten geändert, gesetzt werden.
/// </remarks>
public string ReferenceKey
{
    get { return this.referenceKey; }
    set { this.referenceKey = value; }
}

/// <summary>
/// Die WebServiceProxy-Klasse, die das DataSet liefert.
/// </summary>
/// <value>Setzt den Wert des privaten Feldes proxy oder gibt ihn zurück.</value>
/// <remarks>
/// Falls der übergebene WebServiceProxy <see cref="T:System.Web.Services.Protocols.SoapHeader"/>
/// oder andere Methoden zur Authentifitierung verwendet, muss dieser an die Eigenschaft
/// <see cref="P:TestSpace.Web.SqlSiteMapCacheHandler.ProxyAuhHeader"/> übergeben werden.
/// </remarks>
public object Proxy
{
    get { return this.proxy; }
    set { this.proxy = value; }
}

/// <summary>
/// Die <see cref="T:System.Web.Services.Protocols.SoapHeader"/>-Klasse,
/// die zur Authentifizierung verwendet wird.
/// </summary>
/// <value>Setzt den Wert des privaten Felds proxyAuthHeader oder gibt ihn zurück.</value>
/// <remarks>n/a</remarks>
public object ProxyAuhHeader
{
    get { return this.proxyAuthHeader; }
    set { this.proxyAuthHeader = value; }
}

/// <summary>
/// Die Gültigkeit der AuthHeader Klasse in Sekunden.
/// </summary>
/// <value>Setzt den Wert des privaten Feldes proxyAuthHeaderExpires oder gibt ihn zurück.</value>
/// <remarks>Der Standardwert ist auf 10 Sekunden festgelegt.</remarks>
public int ProxyAuthHeaderExpires
{
    get { return this.proxyAuthHeaderExpires; }
    set { this.proxyAuthHeaderExpires = value; }
}

/// <summary>
/// Der Name der Methode im <see cref="P:TestSpace.Web.SqlSiteMapCacheHandler.Proxy"/>,
/// die das DataSet für den <see cref="T:TestSpace.Web.SqlSiteMapProvider"/> liefert.
/// </summary>
/// <value>Setzt den Wert des privaten Feldes getDataMethod oder gibt ihn zurück.</value>
/// <remarks>Die aufzurufende Methode darf keine Parameter benötigen.</remarks>
public string GetDataMethod
{
    get { return this.getDataMethod; }
    set { getDataMethod = value; }
}

#endregion

Als einzige öffentliche Methode ist die Methode Initialize vorhanden die, nach der Zuweisung der Eigenschaften, einmal aufgerufen wird. In der Global-Klasse sieht das dann in etwa so aus:

public class Global : HttpApplication
{
    // Neue Instanz des WebService-Proxy
    private GetSiteMapProxy siteMapProxy = new GetSiteMapProxy();

    // Neue Instanz vom SoapAuthHeader des WebService-Proxy
    private SoapAuthHeader authHeader = new SoapAuthHeader();
    
    // neue Instanz vom SqlSiteMapCacheHandler
    private SqlSiteMapCacheHandler handler = new SqlSiteMapCacheHandler();
    
    /// <summary>
    /// Standard-Konstruktor
    /// </summary>
    public Global()
    {
        //Auskommentierung der folgenden Zeile bei Verwendung von Designkomponenten aufheben 
        //InitializeComponent();
    }

    /// <summary>
    /// Wird beim Start der Anwendung ausgeführt.
    /// </summary>
    void Application_Start(object sender, EventArgs e)
    {   
        // SqlSiteMapCacheHandler initialisieren
        handler.CacheKey = "sitemapDataSet";
        handler.ReferenceKey = "sitemapKey";
        handler.Proxy = siteMapProxy;
        handler.GetDataMethod = "getData";
        handler.ProxyAuhHeader = authHeader;
        handler.ProxyAuthHeaderExpires = 30;
        handler.Initialize();
    }

    // hier weitere Methoden
}

Wenn die Methode Initialize aufgerufen wird, prüft sich zunächst ob die privaten Felder proxy und getDataMethod mit Werten belegt sind. Anschließend wird überprüft ob die beiden Cacheschlüssel schon im Cache vorhanden sind.

public void Initialize()
{
    // prüfen ob die Felder proxy und getDataMethod mit Werten belegt sind.
    if (this.proxy == null || this.getDataMethod == null)
    {
        throw new EmptyPropertyException(Resources.EmptyProxyAndGetData);
    }
    // prüfen ob die beiden CacheSchlüssel schon im Cache vorhanden sind.
    if (HttpRuntime.Cache[this.referenceKey] == null
        && HttpRuntime.Cache[this.cacheKey] == null)
    {
        HttpRuntime.Cache.Insert(this.referenceKey, false);
        this.CacheSiteMap();
    }
    // Schlüssel sind vorhanden, aber Daten wurden geändert.
    else
    {
        if ((bool)HttpRuntime.Cache[this.referenceKey])
        {
            HttpRuntime.Cache[this.referenceKey] = false;
            this.CacheSiteMap();
        }
    }
}

Sind beide Schlüssel noch nicht vorhanden werden sie ins Cache eingefügt und anschließend die private Methode CacheSiteMap ausgeführt. Sollten die Schlüssel bereits vorhanden sein, wird überprüft ob die Daten im Cache noch gültig sind. Die erwähnte private Methode CacheSiteMap kümmert sich um das eigentliche Speichern des DataSet im Cache, sowie um das erzeugen der CacheDependency.

private void CacheSiteMap()
{
    // den ReferenceSchlüssel in das cacheKeys-Array schreiben.
    // wird zur Überwachung der Gültigkeit des Cache-Objekt benötigt.
    this.cacheKeys[0] = this.referenceKey;

    // Eine Cacheabhängikeit zum referenceKey erzeugen.
    CacheDependency dependency = new CacheDependency(null, this.cacheKeys);

    // Das DataSet in das Cache schreiben.
    HttpRuntime.Cache.Insert(
        this.cacheKey,
        this.GetDataSet(),
        dependency,
        Cache.NoAbsoluteExpiration,
        Cache.NoSlidingExpiration,
        CacheItemPriority.High,
        this.SitemapRemovedCallback);
}

Als zweites Argument erwartet die Cache.Insert-Methode ein Objekt, welches im Cache gespeichert werden soll. Anstatt eines vorliegenden DataSet, wird hier die private Methode GetDataSet aufgerufen, welche das benötigte DataSet liefert. Da die Methode einen beliebigen Webdienst-Proxy, aus der Eigenschaft Proxy, verarbeiten muss, als auch der Methodenname welcher die benötigten Daten liefert auch jedesmal ein anderer sein kann, wird hier ein bisschen Reflection benötigt. Auf diese Art und Weiße kann auch gleich der SoapHeader des Webdienst-Proxy dynamisch erzeugt und zugewiesen werden. Für das Lesen und Zuweisen von Eigenschaften in Klassen die erst zur Laufzeit bekannt sind, bietet das Framework die Klasse PropertyInfo an. Also braucht nur von jeder Eigenschaft der SoapAuthHeader-Klasse ein PropertyInfo-Objekt erzeugt zu werden um die Werte der jeweiligen Eigenschaft zu bearbeiten.

byte[] hash = Fingerprint.CreateHash(
    new object[] { DateTime.Now, this.proxyAuthHeaderExpires, false });

// mittels Reflection die Properties der SoapAuthHeader Klasse holen.
PropertyInfo proxyAuthHeaderValue = this.proxy.GetType().GetProperty("SoapAuthHeaderValue");
PropertyInfo authHeaderCreated = this.proxyAuthHeader.GetType().GetProperty("Created");
PropertyInfo authHeaderExpires = this.proxyAuthHeader.GetType().GetProperty("Expires");
PropertyInfo authHeaderIsEditMode = this.proxyAuthHeader.GetType().GetProperty("IsEditMode");
PropertyInfo authHeaderHashValue = this.proxyAuthHeader.GetType().GetProperty("HashValue");
               
// Die Werte in den Properties der SoapAuthHeader Klasse setzen
if (authHeaderCreated != null)
{
    authHeaderCreated.SetValue(this.proxyAuthHeader, DateTime.Now, null);
}

if (authHeaderExpires != null)
{
    authHeaderExpires.SetValue(this.proxyAuthHeader, this.proxyAuthHeaderExpires, null);
}

if (authHeaderIsEditMode != null)
{
    authHeaderIsEditMode.SetValue(this.proxyAuthHeader, false, null);
}

if (authHeaderHashValue != null)
{
    authHeaderHashValue.SetValue(this.proxyAuthHeader, hash, null);
}
               
// Die SoapAuthHeader-Klasse an das Property SoapAuthHeaderValue der
// WebService-Proxy Klasse übergeben
if (proxyAuthHeaderValue != null)
{
    proxyAuthHeaderValue.SetValue(this.proxy, this.proxyAuthHeader, null);
}

Ähnlich verhält es sich mit dem Methodenname der jeweiligen Webdienst-Proxy-Klasse. Hierfür wird allerdings die MethodInfo-Klasse verwendet. Zuerst wird ein MethodInfo-Objekt mit dem Methodennamen aus dem Feld getDataMethod erzeugt. Anschließend wird geprüft ob das MethodInfo-Objekt null ist und ob der Rückgabe-Typ der Methode ein DataSet ist. Sollten beide Bedingungen erfüllt sein, wird mittels MethodBase.Invoke die Methode im Webdienst-Proxy aufgerufen.

// mittels Reflection die Methode holen welche das DataSet liefert.
MethodInfo proxyGetData = this.proxy.GetType().GetMethod(this.getDataMethod);

// Falls die Methode gefunden wurde und der RückgabeTyp ein DataSet ist
// wird die Methode mit Invoke aufgerufen.
if (proxyGetData != null && proxyGetData.ReturnType == typeof(DataSet))
{
    return (DataSet)proxyGetData.Invoke(this.proxy, new object[] { });
}

Nun muss nur noch das zurückgegebene Objekt in ein DataSet gecastet und als solches zurückgegeben werden. Die oben beschriebene Methode CacheSiteMap fügt dieses jetzt in den Cache ein und kann somit vom SqlSiteMapProvider verwendet werden.

Da die hier beschriebenen Klassen Bestandteil eines größeren Projektes sind, habe ich hier kein Projekt zum Download angeboten. Sollte Interesse bestehen, kann ich die Klassen aus dem Gesamtprojekt herauslösen und zum herunterladen bereitstellen. Einfach kurz einen Kommentar schreiben.

Technorati-Tags: | | |

Zuerst mit Firefox entwickeln, danach mit Internet Explorer

21.06.2008 11:09:00 | Peter Bucher

Ihr werdet mich wohl alle schlagen... ich verwende lieber den Internet Explorer zum Surfen als den Firefox.
Ggf. kann das wieder umkehren, je nach dem wie sich der Firefox entwickelt.

Früher habe ich beim Entwickeln von einem (X)Html / CSS Prototyp immer zuerst im Internet Explorer getestet
und später im Firefox.
Ich musste aber einsehen, das dies ein schlechtes Vorgehen ist.

Fakt ist dass sich der Firefox bzw. alle Mozilla Browser viel besser an den Standard halten, was (X)Html und CSS angeht.
Durch diese Tatsache gibt es weniger Arbeit und der Prototyp kommt viel näher an den Standard.

Wenn schlussendlich alles sauber läuft wird noch mit dem IE und ggf. anderen Browsern getestet.
Die folgenden Anpassungen sind weitaus weniger, als wenn zuerst für den IE entwickelt wird (Auch wenn der Code validiert wird).
Zudem ist es natürlich wichtig, einen Doctype festzulegen und den Code validieren zu lassen.

Es muss nicht immer 100% valid sein - aber die gröbsten Fehler sollten sicher ausgemerzt werden, um gröbere Fehlverhalten auszuschliessen.

CustomControl im App_Code Ordner registrieren

20.06.2008 12:21:00 | Peter Bucher

In einem WebSite-Project (Siehe: VS2008 ASP.NET Web Application Project und Kommentare dazu) gibt es den App_Code Ordner.
Wenn dort ein Control als Codedatei (*.vb / *.cs) angelegt wird, kann dieses nicht wie bei einem externen Projekt oder einem wie bei einem WAP-Projekt referenziert werden.

Normalerweise sieht das Referenzieren so aus:

ASPX:

<%@ Register TagPrefix="pb" Assembly="MeinAssembly" Namespace="MeinControl" %>

Bei einem WAP-Projekt ist "MeinAssembly" der Assemblyname, bei einer externen Bibilothek (DLL) genau so.
Ein Website-Projekt hat aber kein benanntes Assembly, sondern mehrere autogenerierte Assemblies für die komplette Site.

Damit dort auch referenziert werden kann, muss lediglich das Assembly-Attribut weggelassen werden:

ASPX:

<%@ Register TagPrefix="pb" Namespace="MeinControl" %>

Gleiche Vorgehensweise klappt übrigens auch beim Registrieren über die web.config, siehe:

Firefox 2.0 und 3.0 parallel betreiben

20.06.2008 10:43:00 | Peter Bucher

Für alle Webentwickler ein Muss, die Lösung heisst "Firefox portable", da diese nicht auf Profile zurückgreift.
Download unter:

Um beide Versionen parallel laufen zu lassen, gibt es einen Tweak, zu finden unter:

Update (Bessere Lösung mit der normalen Firefox Version):

Via Firefox 2 und Firefox 3 parallel betreiben

Firefox 2 und 3 - Scrollbalken immer da, neue Lösung

20.06.2008 10:31:00 | Peter Bucher

Wie bspw. in Jürgens Posting "CSS: 100% Höhe bei DIVs" erwähnt, gibt es beim YAML-Erfinder eine Lösung damit der Scrollbalken unter Firefox immer erscheint.

Der Trick ist, dem html jeweils auf 100.01% Höhe zu setzen, in etwa so:

CSS:
html { height:100.01%; }

Heute musste ich feststellen, dass diese Lösung mit Firefox 3.0 nicht mehr zieht.

Auf der YAML Webseite fand ich dann eine Lösung, die schlussendlich im Firefox 2.0 sowie auch 3.0 sein Werk tut:

CSS:
html { height: 100%; margin-bottom: 1px; }

Ich muss sagen, das ich enttäuscht bin:

1. Sollte der Scrollbalken immer angezeigt werden, genau wie im IE - alles andere ist IMO Quatsch
2. War das Problem bekannt
3. Wäre eine neue Version dazu da, Probleme zu beheben und nicht noch neue Workarounds hervorzubringen

In diesem Sinne: Firefox Entwickler, bitte macht doch dies dem IE mal nach.

Old School Point & Click Adventure in Silverlight im Stil klassischer SCUMM-Adventures wie Maniac Mansion oder Zak McKracken

20.06.2008 10:20:00 | Steffen Ritter

Wer sagt dass Silverlight-RIAs immer ernsthafte Businessanwendungen oder HD-Videoplayer sein müssen: Buddy Knavery ist ein Point & Click Adventure im Stil der klassischen SCUMM-Adventures wie Maniac Mansion. In dem kostenlosen Adventure übernimmt der Spieler die Rolle des Undercover Cops Buddy Knavery um eine Welle brutaler Verbrechen in Lazarus Falls, Oregon aufzuklären. Direkt zum Spiel Buddy Knavery [Link]

Ach, bei so viel gut gemachtem Retro-Spaß werde ich tatsächlich ein wenig sentimental, erinnert mich das doch an meine Magisterarbeit zu Adventure, Zork, Monkey Island und Silent Hill, damals vor vielen Jahren.

HowTo: PicLens und andere MediaRSS Clients für die eigene Webseite nutzen (MediaRSS mit LINQ to XML erstellen)

19.06.2008 23:52:38 | Robert Mühsig

Dieses HowTo ist im Zusammenhang mit dem normalen “RSS XLINQ” Post entstanden - ist allerdings wesentlich cooler.

Worum geht es?
Es geht um den “MediaRSS” Standard und wie man diesen für sich nutzen kann. Noch nie davon gehört? Ich bis heute auch nicht.
Allerdings gibt es ein bekanntes Firefox Plugin welches mit darauf basiert - die Rede ist von “PicLens“.

image 

Was kann “PicLens”?
“PicLens” bietet eine sehr schicke Oberfläche für verschiedene bekannte Dienste wie YouTube, Google Bildersuche, Flickr oder auch Amazon:

image
Hier mal die Amazon-Ansicht:

image

Media RSS
PicLens kann auch mit dem oben genannten “MediaRSS” Standard umgehen - auf der Seite ist auch gut beschrieben wie man das macht. Da wir bereits ein RSS Feed mit XLinq erstellt haben, dürfte das ja nicht sonderlich schwerer sein.

XLINQ

Wir haben den selben Projektaufbau - nur in der ASHX müssen wir die Erstellung etwas anpassen:

        XNamespace media = "http://search.yahoo.com/mrss";
        XNamespace atom = "http://www.w3.org/2005/Atom";
        public void ProcessRequest(HttpContext context)
        {

            XDocument document = new XDocument(
                                    new XDeclaration("1.0", "utf-8", "yes"),
                                    new XElement("rss",
                                        new XAttribute("version", "2.0"),
                                        new XAttribute(XNamespace.Xmlns + "media", media),
                                        new XAttribute(XNamespace.Xmlns + "atom", atom),
                                        new XElement("channel", this.CreateElements())
                                       ));

            context.Response.ContentType = "text/xml";
            document.Save(context.Response.Output);
            context.Response.End();
        }

        private IEnumerable<XElement> CreateElements()
        {
            List<XElement> list = new List<XElement>();

            for(int i = 1; i < 100; i++)
            {
                XElement itemElement = new XElement("item",
                                            new XElement("title", i),
                                            new XElement("link", "Code-Inside.de"),
                                            new XElement(media + "thumbnail",
                                                new XAttribute("url", "http://code-inside.de/blog/wp-content/uploads/image-thumb" + i + ".png")),
                                            new XElement(media + "content",
                                                new XAttribute("url", "http://code-inside.de/blog/wp-content/uploads/image-thumb" + i + ".png"))
                                       );
                list.Add(itemElement);
            }

            return list;
        }

In dem ASHX Handler haben wir nun noch zwei zusätzliche XNamespaces deklariert. Diese sind (laut der Piclens Seite) notwendig um erstmal dieses XML zu erzeugen:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss" xmlns:atom="http://www.w3.org/2005/Atom">
...
</rss>

Diese Namespaces werden über ein XAttribute hinzugefügt. Der Syntax ist meiner Meinung nach etwas ungünstig - ein “new XNamespace” oder etwas ähnliches hatte nicht funktioniert. Auch ein “new XElement(’xmlns:media’,'…’)” wurde mit einer Exception belohnt - daher dieser Weg.

In der CreateElement Methode müssen wir nur noch die “media:thumbnail” + “media:content” erstellen und fertig sind wir. Zusätzlich könnte man noch die anderen Elemente des Standards einbauen - schaut einfach nochmal in den Guide.

Ergebnis:
Da wir in unserem Head immer noch den Link zum RSS Feed angegeben haben, prüft PicLens automatisch ob man die Bilder auf der “Wall” anzeigen kann:

image

image 
Wer also viele Bilder auf seiner Webseite hat, könnte dies doch leicht umsetzen - insbesondere da dies ein offener Standard (Specification @ Yahoo) ist und ich davon ausgehe, dass sowas noch häufiger eingesetzt wird. Ob nun PicLens als Client ist ja am Ende auch egal :)

[ Download Source Code ]

PS: Als Bildquelle hab ich mal den Blog genommen - bitte aus Trafficgründen nicht überstrapazieren ;)

ShareThis

HowTo: RSS Feeds mit LINQ to XML erstellen (XLinq)

19.06.2008 23:49:40 | Robert Mühsig

Ein XML zu erstellen ist mit Linq to XML recht einfach - ähnliches habe ich bereits in diesem HowTo beschrieben.

Der Unterschied ist eigentlich nur in der Verwendung und in dem dynamischen anlegen der Items zu finden.

Hier erstmal der Projektaufbau:

image

Damit unsere Besucher auch auf den RSS Feed aufmerksam werden, hab ich noch im HEAD einen Link auf den RSS-Feed gemacht:

<head runat="server">
    <title>Untitled Page</title>
    <link rel="alternate" href="Rss.ashx" type="application/rss+xml" title="" id="rss" />
</head>

Die “Rss.ashx”:

Das Grundgerüst erzeugen wir direkt in der ProcessRequest Methode:

        public void ProcessRequest(HttpContext context)
        {

            XDocument document = new XDocument(
                                    new XDeclaration("1.0", "utf-8", "yes"),
                                    new XElement("rss",
                                        new XAttribute("version", "2.0"),
                                        new XElement("channel", this.CreateElements())
                                       ));

            context.Response.ContentType = "text/xml";
            document.Save(context.Response.Output);
            context.Response.End();
        }

Hier wird die Deklaration gemacht und das XDocument wird in den Response.Output geschrieben. Unsere Items erzeugen wir an einer anderen Stelle - und zwar in der “CreateElements” Methode.

Die “CreateElements“-Methode:

Diese Methode gibt IEnumberable<XElement> zurück und kann somit direkt in den Baum eingefügt werden:

private IEnumerable<XElement> CreateElements()
        {
            List<XElement> list = new List<XElement>();

            for (int i = 1; i < 100; i++)
            {
                XElement itemElement = new XElement("item",
                                            new XElement("title", i),
                                            new XElement("link", "http://code-inside.de")
                                       );
                list.Add(itemElement);
            }

            return list;
        }

Sehr einfach und schnell gemacht :)

[ Download Source Code ]

ShareThis

Massenhaft Silverlight 2-Trainingsvideos in Deutsch und Englisch

19.06.2008 16:01:00 | Steffen Ritter

Mein Kollege Oliver Scheer zeichnet momentan eine zehnteilige Einführung in Silverlight 2 als Webcasts auf. Oliver -- der auch die immer ausgebuchten Silverlight-Workshops gehalten hat, beispielsweise auf der Flashforum-Konferenz (http://www.flashforum.de/ffk08/workshops.php#Silverlight%202) oder regelmäßig für Partner und Kunden in den Microsoft-Regionalbüros (http://www.event-team.com/events/silverlightworkshop//Agenda.aspx) -- beginnt mit einer Einführung in die Werkzeuge für Silverlight und den Silverlight-Grundlagen, gibt einen ausführlichen Einblick in die XAML-Technologie, erklärt die Programmierung von Silverlight 2 in JavaScript und Managed Code -- und demonstriert natürlich den Einsatz des (unglaublich coolen, kostenlosen) Deep Zoom Composer (http://www.microsoft.com/downloads/details.aspx?FamilyID=457B17B7-52BF-4BDA-87A3-FA8A4673F8BF&displaylang=en). Abgerundet wird die Serie durch ein Kapitel zum Entwickeln eigener Controls und eine Einführung in den Expression Encoder.

Die ersten Videos von Oliver Scheer sind bereits online zum Ansehen und als Download verfügbar, der Rest kommt in Kürze: http://www.microsoft.com/germany/msdn/webcasts/serien/MSDNWCS-0806-01.mspx

Auch die Kollegen aus England waren äußerst fleißig und haben circa 50 kurze Clips zu Silverlight 2 veröffentlicht. Diese englischen Clips zeichnen sich vor allem dadurch aus, dass sie sehr kurz und knackig sind und aufgrund der thematischen Sortierung auch perfekt als kurze Nachschlagewerke dienen können. Themen sind Basics wie Layout, Controls und Data Binding, aber auch fortgeschrittene Themen wie Sockets, Cross-Domain-Anfragen, Multithreading und HTML-Interop in Silverlight. Und als wäre das nicht genug gibt es noch ein paar Kniffe für alle, die schon mehr Ahnung von Silverlight haben, beispielsweise zum dynamischen Laden von Assemblies, zur Einbettung von Schriften und für individuelle Splash Screens.

Hier wird jeder fündig, egal ob Silverlight-Einsteiger oder bereits RIA/Silverlight-Profi: http://www.miketaulty.com/SLVideos.html

Virtual Earth + Silverlight Deepzoom

19.06.2008 07:59:42 | Robert Mühsig

Im Blog von Chris Pendleton (Virtual Earth Tech Evangelist) bin ich auf ein paar interessante Kombinationen von Silverlight und Virtual Earth gestoßen.

Genauer gesagt, geht es um die “Deepzoom” Funktionalität, hier ein (etwas ruckliges) YouTube Video:

“DeepEarth” ist gehostet auf Codeplex und kann auch hier live angeschaut werden.
Etwas cooler gemacht ist der “Silverlight Map Viewer” von IDV Solutions (allerdings ohne Source Code).

Fazit: Ziemlich cool :)
PS: Im Blog von Chris tauchte auch die Frage auf, warum MS sowas nicht selber anbietet - einfach mal die Kommentare durchlesen.

ShareThis

Windows Vista - Beide Prozessoren bzw. Kerne beim Bootvorgang benutzen

18.06.2008 23:58:00 | Peter Bucher

Gerade eben bin ich auf ein nettes HowTo-Video gestossen, Zitat:

In diesem VideoTutorial wird gezeigt, wie Sie Windows Vista so einstellen können, dass beim Bootvorgang alle bzw. mehrere Prozessoren genutzt werden. Auch wenn Sie einen Dual- oder Mehrcore-Prozessor besitzen, ist Vista standardmäßig so eingestellt, dass nur ein Prozessor genutzt wird.

Naja, da fragt sich manch einer doch: Wieso ist das nicht standardmässig immer auf das höchste eingestellt?

PS: Ob es was bringt, sehe ich beim nächsten Booten ;-)
PPS: Geht natürlich auch für Windows Server 2008.

Deutsche Testversionen von Expression Studio 2 ab sofort verfügbar

18.06.2008 14:21:00 | Steffen Ritter

Die deutschen Testversionen von Expression Studio stehen ab sofort als kostenlose Downloads bereit: http://www.microsoft.com/downloads/results.aspx?DisplayLang=de&nr=20&productId=C0037913-9E11-4A2D-8FD1-0BA441296CBC&freetext=Expression&sortCriteria=date 

<Marketing Copy>Microsoft Expression Studio 2 ist die neue Familie professioneller Werkzeuge für Webdesigner, Interactive Designer und Entwickler von Rich Internet Applications. Designer erhalten mit Expression Studio das perfekte Komplettpaket zum Entwerfen von standardkonformen Websites, Benutzeroberflächen von Windows-Anwendungen und plattformübergreifenden Webanwendungen mit Microsoft Silverlight – so einfach, sicher und schnell wie nie zuvor. </Marketing Copy>

Expression Studio 2 ist in Deutschland ab 7. Juli im Handel.

LAN-Scan, aber richtig schnell! Teil - 3

17.06.2008 20:47:42 | Klaus Bock

Wie im vorherigen Artikel angesprochen, will ich dieses mal die Klasse ClientList, welche die Ergebnisse des Scans speichert, vorstellen.  Zunächst galt es ein Basiselement festzulegen welches mehre Datenreihen, jede Reihe bestehend aus verschiedenen Datentypen, speichert und dabei noch leicht zu handhaben ist. Von eigenen Konstrukten bin ich schnell abgerückt und habe mich für die DataTable entschieden. Sie besitzt alle notwendigen Eigenschaften die ich benötige:

  • sie ist serialisierbar, zur Verwendung mit anderen Prozessen
  • den Export der Ergebnisse im XML-Format gibt es gratis dazu
  • sie besitzt mit dem DataTableReader einen schnellen und einfachen Vorwärts-Cursor
  • das jeweilige Ergebnis kann, in Form einer DataRow, an der Position des Cursors eingefügt oder ersetzt werden.

Einziger Wermutstropfen ist das Einfügen einer DataRow in Multithread-Anwendungen. Zitat MSDN:

Dieser Typ ist bei Multithread-Lesevorgängen sicher. Sie müssen alle Schreibvorgänge synchronisieren.

Doch dazu später mehr.

Als erstes werden die Namen und der Datentyp der jeweiligen Spalte festgelegt. Benötigt wird hier: die IP-Adresse, der Hostnamen und der Online-Status eines jeden LAN-Client. Der Einfachheit halber habe ich mich entschieden die IP-Adresse als Zeichenfolge zu speichern, der Hostnamen ist sowieso eine Zeichenfolge und der Online-Status wird als boolscher Wert abgelegt. Da ich auf die DataTable in der ganzen Klasse zugreifen will, wird sie als Class Member instanziiert um im Konstruktor initialisiert und auch gleich das Schema festgelegt:

public ClientList()
{
    // Mutex initialisieren
    this.mutex = new Mutex(false);

    // neue DataTable erzeugen und das Schema festlegen
    this.table = new DataTable("Clients");
    this.table.Locale = CultureInfo.CurrentCulture;
    this.table.Columns.Add("IpAddress", typeof(string));
    this.table.Columns.Add("Name", typeof(string));
    this.table.Columns.Add("Online", typeof(bool));
}

So ist gewährleistet, dass bei der Instanziierung der Klasse die DataTable immer richtig erzeugt wird.

Da ich aber nicht nur Daten anfügen will sondern sich der Online-Status eines Clients bei einem wiederholten Scan geändert haben kann, muss es auch die Möglichkeit geben Daten zu ersetzen. Leider bietet die DataTable keine Möglichkeit dazu. Doch mit einem kleine Kniff ist auch das zu lösen. Die DataTable bietet über die DataRowCollection die Methoden RemoveAt(int index) und InsertAt(DataRow row, int pos). Nacheinander ausgeführt hat man auch eine Update-Methode.

// da keine Update-Methode zur Verfügung steht,
// Reihe erst löschen und am selben Index einfügen
this.table.Rows.RemoveAt(i);
this.table.Rows.InsertAt(row, i);

Nachdem die Handhabung der Daten in der DataTable geklärt ist, steht noch die Threadsicherheit aus. Als Mechanismus zur Synchronisierung habe ich nicht die lock-Anweisung gewählt, sondern mich für die Verwendung eines Mutex entschieden. Aus meiner Sicht ist der Mutex ein sehr flexibles Werkzeug und lässt sich auch in einem Fehlerfall noch sehr gut handhaben. Ein zweiter wichtiger Punkt ist die Möglichkeit, für eine spätere Erweiterung der Klasse, den Mutex als systemweiten Mutex in einer Eigenschaft nach außen verfügbar zu machen und als WaitHandle zu nutzen. Der entscheidende Punkt für mich war jedoch die Möglichkeit bei einem Threadfehler, z.B. wenn ein Thread in der Bearbeitung abgebrochen wird der gerade den Mutex besitzt, diesen Fehler abzufangen und den Mutex noch freigeben zu können. Nach dem ein Thread beendet oder abgebrochen wurde der gerade im Besitz des Mutex war, geht der Mutex auf den nächsten Thread in der Warteschlange über. In diesem nächsten Thread wird eine AbandonedMutexException ausgelöst, auf die reagiert werden kann. Ich verwende in der ClientList Klasse die Eigenschaft AddedSuccessfull die der aufrufenden Methode signalisiert ob das Ergebnis in die DataTable geschrieben werden konnte. Diese Eigenschaft gibt die private Member Variable addedSuccessfull zurück. Diese private Variable wird mit dem Schlüsselwort volatile instanziiert um zu gewährleisten, dass auch wirklich der letzte zugewiesene Wert der Variablen zurückgegeben wird. Im try-catch Block wird nun im catch diese Variable auf false gesetzt und somit der aufrufenden Methode signalisiert, dass die Daten nicht geschrieben werden konnten. Hier ein Beispiel:

try
{
    // aktuellen Thread blockieren um
    // sicher die Daten in der DataTable zu bearbeiten
    this.mutex.WaitOne();

    // weiterer Code
}
catch (AbandonedMutexException)
{
    this.addedSuccessfull = false;
}
finally
{
    // Mutex auch im Fehlerfall freigeben
    this.mutex.ReleaseMutex();
}

Somit kann sichergestellt werden, dass die Ergebnisse auch wirklich sauber geschrieben werden. Zumindest solange, bis jemand den Gegenbeweis antritt. Für interessierte füge ich hier noch als letztes Listing die komplette Klasse an. Die XML-Kommentare habe ich noch nicht erstellt, aber davon abgesehen sollte die Klasse komplett sein.

internal class ClientList : IDisposable
{
    #region Fields

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    private volatile bool addedSuccessfull;

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    private bool disposed;

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    private Mutex mutex;

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    private DataTable table;

    #endregion Fields

    #region Constructors

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    public ClientList()
    {
        // Mutex initialisieren
        this.mutex = new Mutex(false);

        // neue DataTable erzeugen und das Schema festlegen
        this.table = new DataTable("Clients");
        this.table.Locale = CultureInfo.CurrentCulture;
        this.table.Columns.Add("IpAddress", typeof(string));
        this.table.Columns.Add("Name", typeof(string));
        this.table.Columns.Add("Online", typeof(bool));
    }

    #endregion Constructors

    #region Public Properties

    /// <summary>
    /// 
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    public bool AddedSuccessfull
    {
        get { return this.addedSuccessfull; }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <value></value>
    /// <remarks></remarks>
    public DataTable ClientTable
    {
        get
        {
            return this.table;
        }
    }

    #endregion Public Properties

    #region Private Methods

    /// <summary>
    /// 
    /// </summary>
    /// <param name="disposing"></param>
    /// <remarks></remarks>
    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                this.table.Dispose();
                this.mutex.Close();
            }
        }
        this.disposed = true;
    }

    #endregion Private Methods

    #region Public Methods

    /// <summary>
    /// 
    /// </summary>
    /// <param name="ipAddress"></param>
    /// <param name="name"></param>
    /// <param name="online"></param>
    /// <exception cref="T:System.ArgumentException">
    /// 
    /// </exception>
    /// <exception cref="T:System.ObjectDisposedException">
    /// 
    /// </exception>
    /// <remarks>n/a</remarks>
    public void AddOrReplace(string ipAddress, string name, bool online)
    {
        // prüfen ob Dispose bereits ausgeführt wurde
        if (this.disposed)
        {
            throw new ObjectDisposedException("ClientList");
        }

        this.addedSuccessfull = false;

        // ip-String auf Gültigkeit prüfen
        if (!IPHelper.ValidateIP(ipAddress))
        {
            throw new ArgumentException("Die übergebene Zeichenfolge ist keine gültige IP-Adresse.");
        }

        try
        {
            // aktuellen Thread blockieren um
            // sicher die Daten in der DataTable zu bearbeiten
            this.mutex.WaitOne();

            // neue DataRow zum Anfügen oder Ersetzen initialisieren
            DataRow row = table.NewRow();
            row[0] = ipAddress;
            row[1] = name;
            row[2] = online;

            // Index initialisieren
            int i = 0;

            // prüfe ob die DataTable bereits Elemente enhtällt
            if (this.table.Rows.Count > 0)
            {
                // neuen DataTableReaser mit der lokalen DataTable initialisieren
                DataTableReader reader = new DataTableReader(this.table);

                // durch die DataTable itterieren
                while (reader.Read())
                {
                    // IP-Adressen vergleichen
                    if (string.Equals(reader.GetString(0), ipAddress, StringComparison.Ordinal))
                    {
                        // IP-Adresse ist vorhanden
                        // prüfe ob Änderungen in der Reihe
                        if (!string.Equals(reader.GetString(1), name, StringComparison.OrdinalIgnoreCase)
                            || reader.GetBoolean(2) != online)
                        {
                            // da keine Update-Methode zur Verfügung steht,
                            // Reihe erst löschen und am selben Index einfügen
                            this.table.Rows.RemoveAt(i);
                            this.table.Rows.InsertAt(row, i);

                            // signalisieren, dass die DataTable erfolgreich bearbeitet wurde.
                            this.addedSuccessfull = true;

                            // Reihe ersetzt, beenden
                            return;
                        }
                        else
                        {
                            // signalisieren, dass die DataTable erfolgreich bearbeitet wurde.
                            this.addedSuccessfull = true;

                            // keine Änderung, beenden
                            return;
                        }
                    }
                    // Index erhöhen
                    i++;
                }
            }

            // DataRow anhängen
            this.table.Rows.Add(row);

            // signalisieren, dass die DataTable erfolgreich bearbeitet wurde.
            this.addedSuccessfull = true;

        }
        catch (AbandonedMutexException)
        {
            this.addedSuccessfull = false;
        }
        finally
        {
            // Mutex auch im Fehlerfall freigeben
            this.mutex.ReleaseMutex();
        }
    }

    /// <summary>
    /// 
    /// </summary>
    /// <remarks>n/a</remarks>
    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion Public Methods
}

Parallel Ping Tests

Firefox 3 - erster Eindruck

17.06.2008 20:23:53 | Robert Mühsig

Firefox 3 ist heute erschienen. Natürlich hab ich ihn gleich mal runtergeladen.

Mein erster Eindruck: Nett, nichts weltbewegendes. Schneller auch nicht wirklich (bzw. kann ich es jetzt noch nicht direkt sagen, allerdings funktioniert bei mir erstmal die personalisierte Windows Live Seite nicht ;) )
Die Installation ging ohne Probleme, allerdings gehen momentan nicht alle Addons:

image

Erster Gedanke als Firebug nicht aktiviert war: “Och nö…” ;)

Ansonsten ist das ein oder andere Feature ganz nett - ich denke auch, dass die Plugins nachziehen werden.

Daher Fazit: Solange IETab und Firebug nicht geht, wird er nicht auf meinem Arbeitsrechner installiert werden.

ShareThis

Wöchentliche Rundablage: Ab jetzt bei Del.icio.us

16.06.2008 23:01:31 | Robert Mühsig

image

Die “Wöchentlichen Rundablagen” wurden von mir eingeführt um das, was ich in der Woche so sinnvolles fand, irgendwo zu hinterlegen. Leider “verstopfen” sie etwas die Suchresultate auf dem Blog und auch die Übersicht lässt stark zu wünschen übrig.

Daher habe ich mich nun (mal wieder) bei del.icio.us angemeldet.

Das Management:

Die del.icio.us Seite ist natürlich nett, allerdings will ich lieber meine “Bookmarks” im Firefox managen, dazu gibt es ein Tooles Plugin - das del.icio.us Firefox Plugin, hier mal ein paar Screenshots:

image
“Tagging Management”

Hinzufügen eines neuen Bookmark:

image 
Sehr schönes tagging.

Del.Icio.us Wordpress Integration:

Damit die Links trotzdem irgendwo auf dem Blog auftauchen, habe ich einfach diesen kleine HTML Snippet eingefügt. Das Ergebnis ist folgendes:

image

Sehr viele Links direkt aus meiner “Rundablage”.

Ich hoffe das macht den Blog übersichtlicher :)

Das ist somit der letzte “Wöchentliche Rundablage” Post.

ShareThis

HowToCode "ReadYou": Von Accounts & IDs - das Usersystem

15.06.2008 23:56:26 | Robert Mühsig

Fast jede Anwendung die heute irgendwie läuft, benötigt ein Login oder man kann bestimmte Profildaten anhäufen etc. Daher ist das Usersystem die erste große Hürde, die es zu überwinden gilt.

Solch ein Usersystem ist prinzipell recht schnell gemacht, allerdings muss das Usersystem laut den Anforderungen auch andere Provider (wie OpenID, Windows Live ID etc.) unterstützen.

image

Im Mittelpunkt steht der “User” - in dem Fall ein ganz normaler Nutzer unser Applikation. Ein “User” kann mehrere “Identitäten” haben, z.B. eine Windows Live ID, eine OpenID oder auf ein eigenes Login-System setzen. Ob ich das später tatsächlich einbaue ist eine andere Sache, allerdings sollte das System möglichst “offen” sein.

Dann hat natürlich unser “User” “Accountdaten” (also Profildaten etc.) die wir in unserer Applikation brauchen - die könnte man später z.B. über bestimmte Connectoren befüllen - das ist aber nur eine fixe Idee.

Diese Accountdaten werden momentan durch diese recht einfache Userklasse repräsentiert:

image

Das “Problem” der Zuordnung

Mein selbst festgelegtes Ziel hat natürlich ein Haken: Ein User hat mehrere Identitäten - eine eindeutigen Namen brauch ich also, um genau zu sagen, dass Nutzer A der sich gerade mit seiner Windows Live ID anmeldet auch wirklich Nutzer A ist.
Dafür ist in der User-Klasse der “AccountName” vorgesehen - ein eindeutiger Name der zu beginn vom Nutzer festgelegt wird.
Die “ID” (eine GUID) wird in dem Falle vielleicht nicht mehr benötigt, allerdings könnte ja noch die Anforderunge rein kommen, dass jemand seinen Nutzernamen ändern möchte - daher die “ID“.
Der “IdentificationName” ist der Name, mit dem der User sich gerade angemeldet hat, also wenn er z.B. seine OpenID angegeben hat, steht da “http://user.myopenid.com” da.

Ablauf der Anmeldung / Registrierung

1. User registriert sich mit einer beliebigen Identifikation (”IdentificationName”) und einem Nickname (”AccountName”)
2. User wird im System angelegt und in einer Zuordnungstabelle wird festgehalten, das User A mit der Identifikation A sich anmelden darf.
3. User meldet sich ab.
4. User meldet sich wieder mit seinem “IdentificationName” und dem dazugehörigen Passwort an (seinem OpenID-Passwort z.B.)
5. In der Zuordnungstabelle wird überprüft, ob User X sich mit der ID überhaupt anmelden darf, wenn ja, dann ist er eingeloggt.
6. Wenn er eingeloggt ist, darf er seine Accountdaten weiter pflegen.

Identification-Interface

Momentan ist das Interface noch recht “klein”:

image

Ein Fall fürs Prototyping

Da ich momentan doch noch recht unsicher über das Design bin, werde ich erstmal ein paar Provider “prototypen”, sodass ich sehen kann, ob es überhaupt so machbar wäre.

Fazit & Feedback

Code gibt es heute leider nicht zu sehen, zwar läuft bereits ein UnitTest erfolgreich durch, allerdings muss mal ein, zwei “richtige” Loginsysteme integrieren um eine schlüssige Aussage machen zu können.

Wie immer gilt: Feedback ist gern gesehen - wenn ihr sagt, dass ich mich hier total irre oder etwas wichtige übersehe, dann nur zu :)
Ich bemüh mich in dieser Woche ein Login-System auf die Beine zu stellen. Wenn dies erstmal steht schauen wir weiter ;)

ShareThis

WorldWide Telescope & Backchannel-Diskussion

15.06.2008 22:11:00 | Lori Grosland

WWTScreenshot1 Vor ein paar Jahre war ich auf Hawaii – the Big Island – und bin da auf dem Vulkan Mauna Kea Sterngucken gegangen. Was ich da oben gesehen habe, war unglaublich und fast unbeschreiblich. Die Sterne, die Milchstraße war so klar und so schön. Wenn man in einer großen Stadt wohnt, hat man wenige Möglichkeiten die Sterne wirklich zu betrachten, wie ich auf Mauna Kea gesehen habe. Seit einem Monat habe ich ein neues „Spielzeug“ entdecket, das erlaubt mich die Sterne näher zu betrachten ohne nach Hawaii zu fliegen müssen. WorldWide Telescope ist ein kostenloses Tool das ermöglicht uns, eine virtuelle Reise durch das Universum zu erleben und das Weltall von einem PC zu entdecken und zu erforschen. Auch wenn die Sterne auf Hawaii sehr schön anzuschauen waren, habe ich nicht die Möglichkeit gehabt, mehr über das Universum zu lernen. Aber jetzt kommt das Projekt WorldWide Telescope und macht es einfacher für uns alle über das Weltall und Astronomie zu lernen. WWTScreenshot3

WorldWide Telescope ist ein Projekt von Microsoft Research und hat ein sehr leicht zu bedienendes User-Interface und hat Terabytes von Bilder und Daten. Es basiert auf der "Visual  Experience Engine", die das nahtlose hinein- und heraus- zoomen aus den Bildern ermöglicht. Für die Darstellung von dem Universum werden Bilder aus Observatorien verwendet, die sich auf der ganzen Erde sowie im All befinden. Mann kann das Sonnensystem und Galaxien durchstöbern oder an geführten Touren teilnehmen, die von Astronomen oder Lehrenden großer Universitäten und Planetarien angeboten werden. Wir können auch auswählen, durch welches Teleskop wir alles schauen möchten: wie z. B. das Hubble Space Teleskop, das Chandra X-Ray Observatory Center oder das Spitzer Space Teleskop. Es gibt auch die Möglichkeit die Stellung bestimmter Planeten – in der Vergangenheit, heute und in der Zukunft – zu verfolgen oder das Universum auf unterschiedlichen Wellenlängen des Lichts zu bereisen. Es gibt auch die Möglichkeit ein Teleskop mit einem USB-Anschluß zum WorldWide Telescope anzuschließen und eine Betrachtungsliste (observation list) zu verfolgen.

Mehr Information zu WorldWide Telescope und um die Software downzuloaden finden Ihr hier: http://www.worldwidetelescope.org

Backchannel Diskussion – Online Chat

Bist Du ein Blogger und begeisterte Hobby-Astronomen? Oder würdest gern die Chance haben, mit den Leuten, die WorldWide Telescope gebaut haben, zu chatten? Am 17. Juni gibt eine Online-Chat für Bloggers mit WorldWide Telescope Lead Researcher Curtis Wong und Lead Developer Jonathan Fay. Bloggers weltweit wird an dieser Online-Diskussion teilnehmen und es wird ein Audio und Video-Feed geben. Wenn Du ein Blogger bist und Lust hast, dabei zu sein, kontaktiere mich per Email oder schreibe ein Kommentar auf meinem Blog. Ich bin auch via XING oder Twitter erreichbar. :-)

SQL Server Performance Tips - Initial Datenbankgrösse - Initial Database Size - sp_helpdb

15.06.2008 16:46:00 | Ozgur Aytekin

Es sind diverse Faktoren, warum der Performance einer Datenbank mit der Zeit immer mehr abnehmen kann.

Einer von diesen Faktoren kann vom Anfang an unterdrückt werden, in dem die Default-Werte (Default values) der Datenbankgröße (Database size) bei der Erstellung einer Datenbank definiert werden.

Eine Datenbank auf einem SQL Server erstellen ist wirklich keine großer Hexerei mehr. Einfach Microsoft SQL Server Management Studio starten und dann den SQL Server Instanz wählen. Anschließend über das Kontextmenü New Database

Eine Datenbank auf einem SQL Server erstellen ist wirklich keine großer Hexerei mehr. Im Microsoft SQL Server Management Studio den SQL Server Instanz bestimmen und über Kontextmenü den Befehl New Database auswählen.

image

Im New Database-Dialog kann der Datenbankname (Database name) eingegeben und dann mit OK-Button die Datenbank erstellt werden. So einfach wird eine Datenbank auf einem SQL Server erstellt.

image

Genau an dieser Stelle kann der Initialgröße (Initial Size) einer Datenbank definiert werden. In diesem Beispiel sind die Initial Size Angaben mit 2 und 1 Mbyte definiert. Mit dieser Initial Size-Angaben wird für die Datenbank eine MDF (Data) mit der Größe von 2 Mbyte und eine LDF (Log)-Datei mit der Größe von 1 Mbyte erstellt.

Die Eigenschaft Autogrowth besitzt die Default-Werte von 1 Mbyte bei Data-Datei und 10 Prozent bei Log-Datei. Mithilfe dieser Eigenschaft wird festgelegt, bei, wie viel Mbyte die Dateien vergrößert werden soll.

In diesem Beispiel wird die Data-Datei um 1 Mbyte vergrößert. Und wenn die Daten-Größe 3 Mbyte erreicht wird, dann wird dieser Datei wieder um 1 Mbyte vergrößert.

image

Die automatische Vergrößerung der Data- oder Log-Dateien können die Performance der Datenbank negativ beeinflussen. Dieser Faktor muss bei der Datenbank mit hoher Datenzuwachsrate unbedingt geändert werden, damit die Abstände zwischen den Vergrößerungsaktionen größer sind.

Bei einer Datenmigration von einer anderen Datenbank in einer SQL Server Datenbank sollen die Initial Size-Angaben angepasst werden. Die Größe der Quell-Daten kann für die Initial Size-Eigenschaft der Data-Datei als Initial-Wert definiert werden. So erstellt der SQL Server vom Anfang an eine genügend große Data-Datei und die größer dieser Datei muss während der Datenübernahme vom SQL Server nicht immer wieder vergrößert werden.

Mit Hilfe von sp_help Stored Procedure kann die Größe der Data und Log-Dateien einer Datenbank ermittelt werden.

EXEC sp_helpdb N'SampleDatabase';

image

sp_helpdb liefert für die angegebene Datenbank folgende Informationen:

name: Datenbank-Name

db_size: Die Datenbankgrösse in MB

owner: Den Besitzer der Datenbank

dbid: Interne ID der Datenbank

created: Erstellungsdatum

status: Im Feld Status sind verschiedene Informationen wie aktuller Status, ob der Datenbank nur lesen oder lesen-schreibend verfügbar, ist vorhanden.

compatibility_level: Kompatibilitäts-Level der Datenbank (SQL Server 2000 = 80, SQL Server 2005 = 90 usw.)

Im zweiten Result-Set werden die Angaben pro Datenbank-Datei (MDF und LDF) aufgeführt:

name: Logischer Dateiname

fileid: Interne ID der Datei

filename: Physischer Datei-Pfad und –Name

filegroup: Mit dieser Information wird die Datei-Gruppe angegeben. Bei Log-Dateien ist dieser Angabe NULL.

size: Aktueller Dateigrösse in KB

maxsize: Maximal erlaubter Dateigrösse

growth: Um wie viel der Datei vergrössert wird

usage: Für was dieser Datei benutzt wird. Daten = data only und Log = log only.

Für weitere und detaillierte Informationen kann ich folgenden URL empfehlen:

SQL Server 2005 Books Online - sp_helpdb (Transact-SQL)

Mausfrei für eine Woche, oder: Home, End und Konsorten

13.06.2008 21:55:00 | Peter Bucher

Ich arbeite schon relativ lange mit PCs, aber die Tasten [Home] und [End] habe ich bisher nicht wirklich benutzt.
Letztens habe ich sie entdeckt und ganz bewusst genutzt.

Was soll ich sagen: Einfach nur genial :-)

In Verbindung mit den Tasten [PgUp] und [PgDn] (PageUp und PageDown) lässt es sich super ohne Maus im Code / Text navigieren.
Ich denke das ich mir mal eine Woche ohne Maus gönne, um noch mehr Navigations- und Optionsmöglichkeiten aus der Tastatur rauszuholen und anzugewöhnen.

Wenn dann mal keine Maus zur Verfügung stellt, ist man immer noch schnell und wenn es gut läuft, geht es nur mit der Tastatur noch schneller als mit der Maus kombiniert.
Natürlich ist die Maus für gewisse Anwendungen viel komfortabler und kann aus der Heutigen Zeit nicht mehr weggedacht werden.
Ausserdem hält eine Maus so auch länger (-:

Aber ich denke so eine Woche Mausfrei (Natürlich Privat :-) würde jedem gut tun.
Wie sind eure Meinungen und Erfahrungen zu dem Thema?

Weitere Short-Cuts aus Kommentar von Michal (gabru) entnommen (danke!):

STRG + Cursor Down / Up (Scrollen ohne Cursor zu verschieben)
STRG + Y (Zeile löschen)
ALT + Cursor Down / Up (Zeile nach Oben / Unten verschieben)
STRG + Cursor Links / Rechts (Wörterweise springen)
ALT + PageDown (Zeile klonen)

Passt auch zum Thema: Short-Cuts für Visual Studio im Blog von Stefan Falz:

Änderungen / Korrekturen:
16.06.08 - Kommentar von Michal in den Post eingepflegt.
28.08.08 - Link zu Short-Cuts für Visual Studio von Stefan Falz eingetragen.

LAN-Scan, aber richtig schnell! Teil -2

13.06.2008 19:04:37 | Klaus Bock

Im vorherigen Artikel habe ich das Grundgerüst und den Test der parallelen Verarbeitung beschrieben. Heute will ich den eigentlichen Scan des LAN-Segments zeigen.

Wie in einem früheren Beitrag bereits angesprochen verwenden die Methoden der TPL Delegaten, welche die eigentliche Aufgabe ausführen. Die Anforderungen an eine solche Methode sind also:

  1. Es wird kein Rückgabewert benötigt.
  2. Die jeweilige Hostadresse als Argument entgegen nehmen.
  3. Die jeweilige IP-Adresse in einen Hostnamen auflösen.
  4. Die Gewährleistung das jeder gesendete Ping an den sendenden Thread zurückgegeben wird.
  5. Das empfangene PingReply-Objekt auswerten.
  6. Das Ergebnis threadsicher an ein Objekt übergeben welches die Ergebnisse hält.

Da die Anforderungen nun dargelegt wurden, kann jetzt die Umsetzung in den Code beginnen. Zur Verwendung als Delegat einer TPL Methode, muss das Argument als Typ Object übergeben werden. Sollte dies vergessen werden, wird man vom Compiler sehr schnell daran erinnert. Um Punkt 4. umzusetzen muss der Zugriff auf die Betriebssystemthreads gewährleistet sein. Deshalb muss die Methode die entsprechende Sicherheitsberechtigung anfordern. Genau dafür ist das SecurityPermissionAttribute vorgesehen. Also muss die Signatur wie folgt aussehen:

[SecurityPermission(SecurityAction.Demand,
    Flags = SecurityPermissionFlag.ControlThread)]
private static void CheckHost(object hostNumber)
{
   // hier der Code
}

Zur Gewährleistung von Punkt 4. habe ich die gesamte Logik zwischen die Methoden Thread.BeginThreadAffinity und Thread.EndThreadAffinity eingeschlossen. Zur Auflösung der IP-Adresse in einen Hostnamen, stellt das .NET-Framework die Dns-Klasse zur Verfügung. Um jedoch die Methoden der Klasse nutzen zu können, muss zunächst die als Zeichenfolge vorliegende IP-Adresse in ein IPAddress-Objekt konvertiert werden.

// IPAdress-Objekt erzeugen
IPAddress ipAdress = IPAddress.Parse(netSegment + hostNumber.ToString());

// den HostName der IP-Adresse ermitteln
string hostName = Dns.GetHostEntry(ipAdress).HostName;

Falls die IP-Adresse nicht aufgelöst werden kann, gibt die Eigenschaft HostName der Dns-Klasse die IP-Adresse wieder zurück. Da mir dieses Verhalten nicht gefällt, überprüfe ich ob es sich bei der zurückgegebenen Zeichenfolge um eine IP-Adresse handelt. Mit der Regex-Klasse ist dies einfach zu bewerkstelligen.

private const string ipPattern = @"((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)";

public static bool ValidateIP(string ipAddress)
{
    if (Regex.IsMatch(ipAddress, ipPattern, RegexOptions.CultureInvariant))
    {
        return true;
    }

    return false;
}

Da die Verwendung der Anwendung nur in einem LAN Vorgesehen ist, können die PingOptions mit entsprechend niedrigen Werten konfiguriert werden. In einem LAN kommen eher selten mehr als zwei Weiterleitungen vor. Der Standard-Timeout der Ping-Klasse von 5 Sekunden ist für eine Verwendung in einem LAN wohl auch etwas zu hoch angesetzt.

Zum auswerten des Status des PingReply-Objekts reicht es im allgemeinen auf die Zustände Success, DestinationHostUnreachable und TimedOut der IPStatus Enumeration zu prüfen.

Um die einzelnen Ergebnisse threadsicher an das Objekt zu übergeben welches die Ergebnisse speichert, muss dieses Objekt einen Mechanismus zur Threadsicherheit bereitstellen. Dieses Objekt, in meinem Fall die Klasse ClientList auf die ich später noch eingehe werde, bietet die Eigenschaft AddedSuccessfull welche signalisiert, dass das Ergebnis erfolgreich gespeichert wurde. In dieser Klasse wird ein Mutex verwendet, der den Zugriff der Thread's regelt. Die genannte Eigenschaft, vom Typ bool, wird verwendet um zu überwachen wann das Sichern eines Ergebnis erfolgreich war.

// den ReplyStatus auswerten
switch (reply.Status)
{
    // Ping erfolgreich
    case IPStatus.Success:
        while (true)
        {
            clientList.AddOrReplace(ipAdress.ToString(), hostName, true);
            if (clientList.AddedSuccessfull)
            {
                break;
            }
            Thread.Sleep(10);
        }
        break;

        // weitere Code
}

Sollte der Zugriff zum Speichern durch einen anderen Thread kurzzeitig verhindert werden, wird nach einer kurzen Pause erneut versucht zu Sichern. Auf diese Weiße kann gewährleistet werden, dass die Ergebnisse tatsächlich so geschrieben werden wie sie vorliegen und nicht von einem zweiten Thread mit einem anderen Ergebnis überschrieben werden.

Der Vollständigkeit halber hier noch die komplette Methode:

[SecurityPermission(SecurityAction.Demand,
     Flags = SecurityPermissionFlag.ControlThread)]
 private static void CheckHost(object hostNumber)
 {
     // Pingprüfung an den physischen Betriebssystem-Thread binden
     Thread.BeginThreadAffinity();

     // neuen Ping initialisieren
     Ping pingSender = new Ping();

     // Ping-Optionen festlegen
     PingOptions options = new PingOptions(2, true);
     PingReply reply;
     int timeOut = 60;
     byte[] buffer = new byte[32];

     // IPAdress-Objekt erzeugen
     IPAddress ipAdress = IPAddress.Parse(netSegment + hostNumber.ToString());

     // den HostName der IP-Adresse ermitteln
     string hostName = Dns.GetHostEntry(ipAdress).HostName;

     // prüfe ob die IP-Adresse als HostName zurückgegeben wird.
     if (IPHelper.ValidateIP(hostName))
     {
         // HostName als unbekannt kennzeichnen
         hostName = "Unbekannt";

         // Ping senden und Antwort an das reply-Objekt übergeben
         reply = pingSender.Send(ipAdress, timeOut, buffer, options);

         // wenn Ping erfolgreich, IP-Adresse mit HostName unbekannt eintragen.
         if (reply.Status == IPStatus.Success)
         {
             while (true)
             {
                 clientList.AddOrReplace(ipAdress.ToString(), hostName, true);
                 if (clientList.AddedSuccessfull)
                 {
                     break;
                 }
                 Thread.Sleep(10);
             }
         }
     }
     else
     {
         // Ping senden und Antwort an das reply-Objekt übergeben
         reply = pingSender.Send(hostName, timeOut, buffer, options);

         // den ReplyStatus auswerten
         switch (reply.Status)
         {
             // Ping erfolgreich
             case IPStatus.Success:
                 while (true)
                 {
                     clientList.AddOrReplace(ipAdress.ToString(), hostName, true);
                     if (clientList.AddedSuccessfull)
                     {
                         break;
                     }
                     Thread.Sleep(10);
                 }
                 break;

             // Host nicht erreichbar
             case IPStatus.DestinationHostUnreachable:
                 while (true)
                 {
                     clientList.AddOrReplace(ipAdress.ToString(), hostName, false);
                     if (clientList.AddedSuccessfull)
                     {
                         break;
                     }
                     Thread.Sleep(10);
                 }
                 break;

             // Zeit abgelaufen
             case IPStatus.TimedOut:
                 while (true)
                 {
                     clientList.AddOrReplace(ipAdress.ToString(), hostName, false);
                     if (clientList.AddedSuccessfull)
                     {
                         break;
                     }
                     Thread.Sleep(10);
                 }
                 break;
         }
     }

     // Thread-Bindung aufheben
     Thread.EndThreadAffinity();
 }

Die Klasse ClientList, welche die Ergebnisse zur weiteren Verwendung speichert, werde ich im dritten und letzten Teil vorstellen.

MbUnit 2.4 PlugIn for ReSharper 4.0 Final

13.06.2008 15:12:50 | Albert Weinert

Update (20.06):

Beta 9 is ready ReSharper-MbUnit-Beta9.zip it fixes Issues 11, 17, 18 and some other things. Go and get it and please provide feedback. If work stable it will be included in the next MbUnit 2.4 Update.
Know Issues: DependsOn() and DataFixture() not supported in R# (not fixable yet)

A new Version of the PlugIn to Run MbUnit 2.4 Test in ReSharper is available.

You can download it: ReSharper-MbUnit-Beta8.zip

There is also an Update for ReSharper 3.1 which fixes some bugs..

Know-Issues:

The results of an RowTest with Enums are not displayed in the Unit Test Explorer.

Have fun.

Technorati-Tags: ,,

Events für User- und CustomControls definieren und benutzen

12.06.2008 21:44:00 | Peter Bucher

In der Welt von ASP.NET gibt es - im Gegensatz zu Classic ASP - ein Eventsystem das in der ASP.NET Engine verwurzelt ist.
Dazugehörend haben viele ASP.NET Controls Events. Das bekannteste ist wohl Button.Click, über diesen Event kann festgestellt werden ob ein bestimmter Button gedrückt wurde, oder nicht.

Nun gibt es ja die Möglichkeit eigene User- und CustomControls zu schreiben.
Diese können mit Events ausgestattet werden, und genau um das geht es hier in diesem Artikel.

Wie funktionieren die Events in ASP.NET?

Grundsätzlich ereignen sich alle Aktionen auf dem Client, bspw. klickt eine Person an einem Computer in dessen Webbrowser auf einen Button.
Danach wird das Formular abgesendet und der Server empfängt dieses.

Der Server leitet den Http-Request (Die Http-Anfrage) an die ASP.NET Engine weiter.
Die Engine findet dann anhand der <Control>.UniqueID (Siehe: Identifizierung von Controls: Control.ID / .ClientID / .UniqueID) das Control zu dem die jeweiligen Daten gehören.

Wenn dieses Control gefunden wurde, leitet die Engine die gesamten POST-Daten (Formulardaten) an das bestreffende Control weiter.
Sind die Daten geändert worden, bzw. befinden sich die Daten in einem erwarteten Zustand, löst das Control den Event aus, ansonsten nicht.

Dies ist die normale Vorgehensweise von Events (Die Page Events laufen ähnlich ab), die über POST (Formular, Postback) ausgelöst werden.

Wie definiere ich einen Event, oder was ist ein Event überhaupt?

Für die Grundlagen zur Event-Erstellung, mit oder ohne nutzerspezifische Argumente, verweise ich hier auf den FAQ Thread des C#-Forums myCSHarp.de.

Die FAQ von myCSharp.de als solches ist auch für andere Sprachspezifische Angelegenheit sehr zu empfehlen.

Wie / Wann werden die Events ausgelöst und gibt es mehrere Auslösearten?

Das trickreiche an Events in ASP.NET ist nicht die Dekleration der Events, sondern der Zeitpunkt an dem sie ausgelöst werden sollen.
Es gibt tatsächlich mehrere Arten von Zustandsveränderungen bei denen ein Event ausgelöst werden kann.

Folgend die Möglichkeiten von Zustandsänderungen durch einen Benutzer der Web Applikation ausgelöst:

  • Werte die durch einen (oder mehrere) GET-Parameter (QueryString) empfangen werden, bspw. wenn der Benutzer auf einen Link klickt
  • Werte die durch einen (oder mehrere) POST-Parameter (Form) empfangen werden, bspw. wenn der Benutzer Formularfelder ausgefüllt hat und dann auf einen Button klickt, oder aber nur auf einen Button klickt

Zudem gibt es noch weitere Faktoren und Möglichkeiten, die als Indikatoren für eine Eventauslösung benutzt werden können:

  • Werte die durch einen (oder mehrere) POST-Parameter empfangen werden, die durch die __doPostback-Javascriptfunktion abgeschickt werden
  • Zustandsänderungen aller Art auf der Serverseite (Datei, Datenbank, Session, Zeit, Datum, etc...
  • Festgelegte Logik im Code, bspw. Vergleiche, etc...

Aller Anfang ist schwer leicht

Zuerst folgt ein Beispiel das auf einen GET-Parameter reagiert und bei Vorhandensein des Parameters einen Event auslöst.

C#:


namespace pb.Web.UI.WebControls.Examples
{
    public class TestControlGetSimple : WebControl 
    {
        public event EventHandler ParameterAvailable;

        protected virtual void OnParameterAvailable(object sender, EventArgs e) {
            if (this.ParameterAvailable != null)
                this.ParameterAvailable(sender, e);
        }

        protected override HtmlTextWriterTag TagKey {
            get {
                return HtmlTextWriterTag.Div;
            }
        }

        protected override void OnInit(EventArgs e) {
            base.OnInit(e);

            HyperLink link = new HyperLink();
            link.ID = "hlTest";
            link.Text = "Testlink";
            link.NavigateUrl = "?test=1234";
            this.Controls.Add(link);
        }

        protected override void OnLoad(EventArgs e) {
            base.OnLoad(e);

            string name = this.Context.Request.QueryString["test"];
            if(!string.IsNullOrEmpty(name)) {
                this.OnParameterAvailable(this, EventArgs.Empty);
            }
        }
    }
}

Was genau passiert da jetzt?

Das Beispiel ist einfach aufgebaut. Zuerst wird ein Event mit einem Standard-Eventhandler (in .NET verfügbar) implementiert.
Danach wird die eventauslösende OnEvent-Methode definiert, die aufgerufen wird, wenn der Event ausgelöst werden soll.

Anschliessend wird per überschriebenen TagKey-Eigenschaft angegeben, dass das Control ein umschliessenden Div-Tag besitzen soll. (Im Beispielprojekt wird das für die Formatierung benutzt).
In der OnInit-Methode wird ein Objekt vom Typ Hyperlink angelegt, ein Text und eine Url + GET-Parameter angegeben und anschliessend zum Control hinzugefügt.

Jetzt kommt der spannende Teil: Das Event wird ausgelöst.

Beim Laden des Controls wird ein evt. vorhandener Parameter über <HttpContext>.Request.QueryString(<Key>) abgerufen, und anschliessend überprüft ob dieser null oder ein Leerstring (String.Empty) ist.

Falls dies nicht der Fall ist, wird der Event ausgelöst. Das war schon der ganze "Zauber" auf der Control-Seite.

Die Benutzung des Controls in einer ASP.NET Seite ist sehr einfach und geht so von der Hand, wie ansonsten ein Klick-Event eines Buttons abonniert wird.

Wie das Control registiert wird, um auf der Seite zu benutzen, kann im folgendenen Blogpost nachgelesen werden:

Beispiel:

ASPX:



<fieldset>
    <legend>GET Beispiel (simple)</legend>
    <pb:TestControlGetSimple ID="testGetSimple" runat="server" />
    <br />
    <asp:Label ID="lblInfoGetSimple" Text="Parameter nicht verfügbar" runat="server" />
</fieldset>

Codebehind (C#):


protected void Page_Load(object sender, EventArgs e) {
    this.testGetSimple.ParameterAvailable += testGetSimple_ParameterAvailable;
}


void testGetSimple_ParameterAvailable(object sender, EventArgs e) {
    this.lblInfoGetSimple.Text = "Parameter ist <strong>vorhanden</strong>";
}

Zuerst werden auf der ASP.NET Seite das Test-Control und ein Label hinzugefügt.
Anschliessend abonnieren wir das Event im Page_Load Eventhandler und fügen dem zugewiesenen Handler Code hinzu, damit das Label anzeigt, ob der Event ausgelöst wurde oder nicht.

Wenn der Link angeklickt wird und infolgedessen ein GET-Parameter vorhanden ist, wird das Event ausgelöst und im Eventhandler den Text des Labels geändert.
Aus Benutzersicht ist nur der geänderte Text sichtbar.

Das Beispiel wurde mit Absicht sehr simpel gehalten, um einen einfach Einstieg zu ermöglichen.

Ein erweitertes Beispiel mit GET-Parametern

Folgend der Code eines erweiterten Beispiels, das mithilfe einer Session erkennen kann, ob der aktuelle Wert des GET-Parametes geändert wurde. Den ASPX-Teil wird nicht mehr gezeigt, da er praktisch identisch zum Beispiel (Siehe Code) ist.

C#:


namespace pb.Web.UI.WebControls.Examples
{
    public class TestControlGet : WebControl, INamingContainer
    {
        public delegate void NameChangedDelegate(object sender, NameChangedEventArgs e);
        public event NameChangedDelegate NameChanged;


        protected virtual void OnNameChanged(object sender, NameChangedEventArgs e) {
            if (this.NameChanged != null)
                this.NameChanged(sender, e);
        }

        protected override HtmlTextWriterTag TagKey {
            get {
                return HtmlTextWriterTag.Div;
            }
        }

        protected override void OnInit(EventArgs e) {
            base.OnInit(e);

            for (int i = 0; i < 5; i++) {
                HyperLink link = new HyperLink();
                link.ID = "testLink" + i.ToString();
                link.Text = "Patrick " + i.ToString();
                link.NavigateUrl = "?name=Patrick" + i.ToString();
                this.Controls.Add(link);
                this.Controls.Add(new LiteralControl("<br />"));
            }
        }

        protected override void OnLoad(EventArgs e) {
            base.OnLoad(e);

            string name = this.Context.Request.QueryString["name"];
            string nameBefore;
            object sessionValue = this.Context.Session["name"];

            if (sessionValue != null)
                nameBefore = sessionValue.ToString();

            bool flag = false;

            // Erste Änderung (ohne Session, erster Aufruf)
            if (name != null && sessionValue == null)
                flag = true;

            // Änderung mithilfe der Session herausfinden
            if (sessionValue != null && name != null && !sessionValue.Equals(name))
                flag = true;

            if(flag)
                this.OnNameChanged(this, new NameChangedEventArgs(name));

            this.Context.Session["name"] = name;
        }
    }

    public class NameChangedEventArgs : EventArgs {
        private string _newName;

        public NameChangedEventArgs(string newName) {
            this._newName = newName;
        }

        public string NewName {
            get { return this._newName; }
        }
    }
}

In diesem Beispiel wird eine Liste von Links mit GET-Parametern ausgegeben.
Wenn der empfangene Parameter ändert, wird der Event ausgelöst.

Beim ersten Empfangen eines Wertes wird dieser in die Session geschrieben und dieser Spezialfall berücksichtigt.
Wird jedoch ein Wert empfangen, wenn die Session schon ein Wert enthält, wird auf Ungleichheit geprüft, falls wahr wird der Event ausgelöst.

Zudem wird in diesem Beispiel ein eigener Delegate benutzt, um eigene EventArgs zu unterstützen.
Im Eventhandler kann der geänderte Namen einfach über e.NewName abgerufen werden.

(Der Einfachheit halber habe ich keinen generischen EventHandler benutzt, obwohl dieser ab .NET 2.0 natürlich zu empfehlen wäre. Wie es geht, siehe: Eigenen Event definieren / Information zu Events (Externer Link: myCSharp.de) )

Einen Event über POST-Werte mithilfe von IPostBackEventHandler und ClientScript.GetPostBackEventReference auslösen

Wie weiter oben schon angesprochen, stellt ASP.NET selber eine Architektur bereit, um mit POST-Daten Events über eine spezielle Infrastruktur auszulösen.
Die jetzt vorgestellte Lösung basiert auf der __doPostBack-Funktion, die ASP.NET im Normalfall in jeder ASP.NET Seite zur Verfügung stellt.

Folgend die Definition der Schnittstelle.

C#:

public interface IPostBackEventHandler
{
    void RaisePostBackEvent(string eventArgument);
}

Im Normalfall geschieht ein Postback über einen Html-Button, der das Formular absendet.
Das Formular kann aber auch per Javascript abgesendet werden, genau das nutzt die __doPostBack-Funktion.
Sie kann zusätzlich Parameter entgegennehmen und diese über HiddenFields (Versteckte Eingabefelder) zum Server schicken.

So ist nicht nur ein Abschicken und Prüfen von Form-Elementen möglich, sondern auch das übergeben beliebiger Elemente und zudem auch den Namen des Controls, von dem die Elemente bzw. Argumente kommen.

Über die Methode GetPostBackEventReference() der Klasse ClientScript finden sich viele Überladungen, die benutzt werden können um eine Javascript Postback-Referenz zu bekommen.
Dies ist nichts weiter als ein benutzerdefinierter Aufruf der __doPostBack-Funktion, mit eigenen Parametern.

ASP.NET stellt eine Schnittstelle Namens IPostBackEventHandler zur Verfügung, die implementiert werden kann, um über Postbacks mit der Möglichkeit der Übergabe von Elementen zu kommunizieren.
Dabei muss immer das Control selber (schlussendlich die UniqueID des Controls) übergeben werden, sowie Argumente nach Wunsch.

Vielleicht kennt der eine oder andere die __doostBack-Funktion aus dem GridView, wenn eine Zeile selektiert wird.
Dies wird genau nach diesem Schema erledigt.

Folgend nun das Beispiel (Wiederum nur das Control an sich, da der Rest praktisch gleich ist):

C#:


namespace pb.Web.UI.WebControls.Examples
{
    public class TestControlPost : WebControl, INamingContainer, IPostBackEventHandler
    {
        public delegate void NameSelectedDelegate(object sender, NameSelectedEventArgs e);
        public event NameSelectedDelegate NameSelected;


        protected virtual void OnNameChanged(object sender, NameSelectedEventArgs e) {
            if (this.NameSelected != null)
                this.NameSelected(sender, e);
        }

        protected override HtmlTextWriterTag TagKey {
            get {
                return HtmlTextWriterTag.Div;
            }
        }

        protected override void OnInit(EventArgs e) {
            base.OnInit(e);

            for (int i = 0; i < 5; i++) {
                HyperLink link = new HyperLink();
                link.ID = "testLink" + i.ToString();
                link.Text = "Patrick" + i.ToString();
                link.NavigateUrl = "#";
                link.Attributes.Add("onclick", this.Page.ClientScript.GetPostBackEventReference(this, "Patrick " + i.ToString()));
                this.Controls.Add(link);
                this.Controls.Add(new LiteralControl("<br />"));
            }
        }

        #region IPostBackEventHandler Members

        public void RaisePostBackEvent(string eventArgument) {
            if(this.NameSelected != null)
                this.NameSelected(this, new NameSelectedEventArgs(eventArgument));
        }

        #endregion
    }

    public class NameSelectedEventArgs : EventArgs
    {
        private string _selectedName;

        public NameSelectedEventArgs(string newName) {
            this._selectedName = newName;
        }

        public string SelectedName {
            get {
                return this._selectedName;
            }
        }
    }
}

Das Beispiel ist dem letzten Beispiel sehr ähnlich, jedoch geht es hier um das Selektieren eines Namens, nicht um die Änderung dessen.

Es werden auch HyperLinks erzeugt, und in dessen clientseitigem onclick-Eventhandler jeweils die PostBackEvent-Referenz mithilfe obengenannter Methode ein parameterisierter Javascript-Aufruf für das jeweilige Link erzeugt.
Dabei wird eine Referenz auf das Control selbst mitgegeben, damit ASP.NET das Control anhand der UniqueID identifizieren kann, und den Namen, der selektiert werden kann.

Die ASP.NET Engine merkt beim Durchgehen aller Controls, dass dieses Control die IPostBackEventHandler Schnittstelle implementiert und feuert dann ggf. bei Bestehen eines Events dass durch das Link gesendet wurde, das Event.

Dies geschieht dadurch, dass ASP.NET die Methode RaisePostBackEvent aufruft und ihr das Argument übergibt, dass vom Client gesendet wurde.
Aufgrund dieser Infos kann schlussendlich im Eventhandler auf das Argument zugegriffen werden.

Das Interface IPostBackDataHandler, Sinn und Nutzen

ASP.NET stellt noch ein weiteres Interface zur Verfügung, um Events auszulösen.
Dieses setzt zwei Methoden voraus, folgend die Definition:

C#:

public interface IPostBackDataHandler
{
    bool LoadPostData(string postDataKey, NameValueCollection postCollection);
    void RaisePostDataChangedEvent();
}

Im Beispiel auf der Seite von Microsoft, ist ein ziemlich sinnloses Beispiel zu finden, indem von einem WebControl ein Button nachgebaut wird.

Diese Schnittstelle wird nur existieren, damit Microsoft alle Form-Controls implementieren konnte.
In LoadPostData werden die UniqueID des Controls selber und die komplette Post-Collection angeboten.

Durch <Collection>[postDataKey] kann der Wert des Controls sehr einfach gelesen werden, der Rückgabetyp ist ein bool und bei einer Rückgabe von true wird die RaisePostDataChangedEvent()-Methode aufgerufen und der oder die Events können gefeuert werden.
ASP.NET ruft - auch durch die Interface-Markierung - jeweils die Methode LoadPostData automatisch auf.
Dies jedoch nur, wenn als TagKey bzw TagName des Controls ein bekanntes Form-Control gesetzt ist (Was auch Sinn macht).
Wenn die Methode bei einem eigenen Control trotzdem aufgerufen werden soll, so muss die Methode RegisterRequiresPostBack (Siehe auch: Understanding what Page.RegisterRequiresPostBack does) von der Klasse Page aufgerufen werden und das zu registrierende Control übergeben werden.

Für eine Implementation der Controls: Button, DropDownList, TextBox, etc... ein sehr sinnvolles Interface.
Bei der Implementation eines eigenen Controls, das kein Form-Tag darstellt geht es m.E. einfacher und besser ohne dieses Interface.

Siehe auch:

An dieser Stelle gibt es kein Beispiel, jedoch ist im Download auch ein solches Beispiel vorhanden, dass das Interface benutzt.

Inhalt des Downloads:

  • Alle hier darstellten Beispiele
  • IPostBackDataHandler-Beispiel
  • Alles in einer Solution mit einem Control-Library Projekt und einem Web Application Project zum Testen

Download:

Dank geht an:
Norbert Eder für das Review des Artikels

 

Ich freue mich über jegliche Kommentare!

Design von barrierefreien ASP.NET-Websites

12.06.2008 14:40:30 | Jürgen Gutsch

Vorhin hat mich Peter Bucher darauf aufmerksam gemacht, das dieses Blog beim googeln nach "asp.net barrierefreiheit" an erster Stelle steht. Tolle Sache :-)

Dabei ist mir eine ältere, aber nicht weniger interessante Ressource aus der MSDN an dritter Stelle des Suchergebnisses aufgefallen:

Design von barrierefreien ASP.NET-Websites (Original von Scott Mitchell)
"Nutzen Sie die Vorteile der Vererbung im .NET Framework und erweitern Sie ASP.NET-Klassen so, dass der generierte ASP.NET-Code für Benutzer mit Behinderungen vollständig zugänglich ist."
http://msdn.microsoft.com/de-de/library/bb978918.aspx

Wenngleich auch die Code-Beispiele schon etwas älter sind, die enthaltenen Infos über barrierefreie Webentwicklung allgemein und WAI, WCAG und Section 508 sind auf alle Fälle interessant.

Design von barrierefreien ASP.NET-Websites

12.06.2008 14:40:30 | Jürgen Gutsch

Vorhin hat mich Peter Bucher darauf aufmerksam gemacht, das dieses Blog beim googeln nach "asp.net barrierefreiheit" an erster Stelle steht. Tolle Sache :-)

Dabei ist mir eine ältere, aber nicht weniger interessante Ressource aus der MSDN an dritter Stelle des Suchergebnisses aufgefallen:

Design von barrierefreien ASP.NET-Websites (Original von Scott Mitchell)
"Nutzen Sie die Vorteile der Vererbung im .NET Framework und erweitern Sie ASP.NET-Klassen so, dass der generierte ASP.NET-Code für Benutzer mit Behinderungen vollständig zugänglich ist."
http://msdn.microsoft.com/de-de/library/bb978918.aspx

Wenngleich auch die Code-Beispiele schon etwas älter sind, die enthaltenen Infos über barrierefreie Webentwicklung allgemein und WAI, WCAG und Section 508 sind auf alle Fälle interessant.

Dunkles Visual Studio 2008 mit ReSharper 4

12.06.2008 14:23:13 | Albert Weinert

Ich selbst benutze schon seit längeren auch einen "dunkles" Visual Studio, ich habe mir damals irgendeine Dark-Theme genommen und über die Zeit an meine Bedürfnisse angepasst. Dazu gehört unter anderem auch die Verwendung einer Schriftart für den Text-Editor die nicht über eine feste Laufweite verfügt Verdana. Darauf hat mich vor einiger Zeit Roland Weigelt drauf gebracht. Zusätzlich wird noch Segio UI für den Rest verwendet.

Proportional und Schwarz

Visual Studio arbeitet damit ohne Probleme und man hat den angenehmen Nebeneffekt das man mehr Text in eine Zeile bekommt. Anfangs ein wenig ungewöhnlich, aber man gewöhnt sich schnell dran. Nur ASCII-Art ist nun ein wenig schwierig.

image

Verdana als proportionale Schriftart deshalb weil man dort das große i von kleinen l sehr gut unterscheiden kann.

image

imageDes weiteren werden bei mir die Operatoren auch noch fett dargestellt, hauptsächlich damit man das ! im Quelltext besser erkennen kann.

Dies noch in einer anderen Farbe und schon kann man den kleinen not-Operator sehr schön erkennen.

Feinarbeit

Was mir bei einigen Dunklen-Themes für Visual Studio aufgefallen ist dass dies nicht sehr ausgearbeitet sind. Dies fällt dann auf wenn man man damit arbeitet.

So ist das Bracket Highlighting oft nicht schön, oder das markieren von Text.

image

image

So etwas habe ich in "langer" Arbeit soweit korrigiert, sicher noch nicht alles.

Visual Studio Bug

Auch wird einem Visual Studio Bug entgegen gewirkt, der bei der umfangreichen Einfärbung die ReSharper vornimmt. Zwischendurch passiert es das die Hintergrund Farben plötzlich falsch sind, dies dadurch behoben dass die Hintergrund Farben explizit gesetzt werden und nicht auf "Default" gelassen worden sind.

ReSharper 4

Die hier hinterlegte Einstellungen funktionieren nur gut mit dem ReSharper 4 und Visual Studio 2008. Dazu muss in den ReSharper-Options noch das erweiterte Einfärben aktiviert werden.

image

Beachtenswertes

Installiert man ein Update des ReSharpers dann muss die Farbpalette erneut importiert werden, sonst werden die Farben nicht richtig gesetzt, ob nun Visual Studio oder ReSharper die "Schuld" daran hat ist nicht wirklich geklärt.

Damit dies auch immer schnell und Einfach funktioniert, sind in den gespeicherten Einstellungen auch nur Fonts & Colors hinterlegt. So das keine anderen Einstellungen überschrieben werden.

Auch die HTML und XML Darstellung ist entsprechend angepasst. Somit das ein vernünftiges arbeiten damit möglich ist. Jedoch empfehle ich es an die eigenen Bedürfnisse anzupassen.

Bestes Dark-Theme?

Das ist natürlich subjektiv ;) Ich bin an weiteren Verbesserungen interessiert, und ich habe sicherlich immer noch nicht alles beachtet um es 100% Rund zu bekommen. Wenn jemand also Anpassungen daran vornimmt bin ich daran interessiert diese zu sehen und selbst zu nutzen. Somit immer raus mit der Sprache.

Download

Wenn jemand anderes die Einstellungen nutzen möchte, dann einfach die Settings-Datei hier herunterladen und in Visual Studio importieren. Fertig.

DarkResharperColors.zip

Web Accessibility Checklist

12.06.2008 08:35:37 | Jürgen Gutsch

Aaron Cannon hat eine interessante Web Accessibility Checkliste veröffentlicht. Aaron Cannon ist ein blinder Webentwickler und Accessibility Consultant.

Die Checkliste kan als PDF oder im Textformat angesehen werden.

Via http://www.webresourcesdepot.com/web-accessibility-checklist/

Web Accessibility Checklist

12.06.2008 08:35:37 | Jürgen Gutsch

Aaron Cannon hat eine interessante Web Accessibility Checkliste veröffentlicht. Aaron Cannon ist ein blinder Webentwickler und Accessibility Consultant.

Die Checkliste kan als PDF oder im Textformat angesehen werden.

Via http://www.webresourcesdepot.com/web-accessibility-checklist/

HowTo: TFS Best Practice - Wie lege ich ein Projekt an?

11.06.2008 23:38:54 | Robert Mühsig

image

Der TFS ist mit seinen ganzen tollen Funktionen sehr vielseitig, allerdings sollte man bereits am Anfang auf eine Sache achten: Wie strukturiere ich das Projekt?
Damit sind keine “logischen” Strukturen gemeint, sondern wie lager ich die Dateien richtig im TFS? Gibt es einen Leitfaden?

Die gute Nachricht: Ja - den TFS Guide!

Hinweis: Im Rahmen des ReadYou-Projektes werde ich den Source Code bei Codeplex (welche auf den TFS einsetzt) versuchen nach diesen Best-Practices unterzubringen.
Da dies allerdings (jedenfalls bei mir) immer mal wieder Kopfzerbrechen bereitet, ein kleines HowTo:

Warum das ganze eigentlich?
Auf der Codeplex Seite des TFS Guides findet man viele Tipps, warum man dies so oder so machen sollte - ein deutlichste Bild nehm ich direkt mal aus den Guide (Seite 50):

image

Erklärung:
Es gibt in der Entwicklung immer einen “Main” Pfad - an diesem Pfad wird ständig gearbeitet. Irgendwann wird ein “Release 1” herausgegeben.
Jetzt beginnt die Phase, in der sicherlich im “Release 1” Bugs auftreten und Fixes etc. nachgeliefert werden müssen. Natürlich muss unser Hauptentwicklungsteam die Software während dieser Zeit im “Main” Pfad  weiterentwickeln können.
Jetzt könnte es aber passieren, dass im “Release 1” ein Fehler auftritt der unbedingt gefixt werden muss - allerdings ist die Entwicklung zum “Release 2” schon sehr weit fortgeschritten. Komplette Codeteile sind neu geschrieben wurden! Ein “Patch” ist daher jetzt nicht machbar.
Das sowas nicht passiert macht man “Branches” (dt.: Zweige) und “merged” (dt.: wieder vereinen) die am Ende wieder (wenn es nötig ist) in den Entwicklungszweig.
Das mal als kurze Erklärung - das PDF gibt noch mehr Hinweise, warum und wieso man sowas machen sollte.

Schritt 1: Workspace anlegen

Als erstes muss auf unser Entwicklungsmaschine ein Workspace zum TFS Projekt her - daher gehen wir auf den Team Explorer -> Soure Control (kann hier runtergeladen werden) und klicken einfach beim Projekt auf “Get Latest Version“:
image

Dort wählt man einen Speicherort aus - nachdem das gemacht wurde, sollte sowas zu sehen sein:
image

Falls man feststellt, dass das Verzeichnis nicht das richtige war, muss man das Mapping im Workspace-Setting umstellen. Um hier rein zu kommen einfach mal hier klicken:
image

Schritt 2: Ordnerstruktur über den Team Explorer / Source Control anlegen

In diesem Schritt legen wir jetzt unsere Ordnerstruktur an - das wird genauso auch auf der Clientseite im Workspace übertragen.
image
 

Am Ende sollte man sowas haben - ein leeres Grundgerüst:
image
 

Schritt 3: Leere Solution anlegen

Jetzt legen wir uns eine leere Solution an, dabei muss die Location auf “[Workspace-Ordner]\Main\Source” stehen!

image

Nachdem das gemacht wurde, sieht man das Solution-File auch bereits im TFS in der Source Control Ansicht:

image

Schritt 4: Projekte anlegen

Die Projekte liegen nicht direkt mit dort, wo die Solution liegt, sondern in einem extra Source Ordner (im TFS Guide steht dazu, warum und wieso ;) ), daher habe ich einfach zwei Solution-Folder “Source” & “Tests” (für die UnitTest-Projekte) angelegt:

image

Wichtig ist hierbei auch, dass als Location auch der “\Source” Unterordner angegeben ist - ansonsten wird es falsch abgespeichert:

image

Tipp: Falls man was auf der Clientseite falsch gemacht hat, muss es auch auf dem TFS berichtigt werden - dazu gibt es einen unscheinbaren “Delete”-Knopf. In der Source Control Sicht den entsprechenden Ordner markieren & löschen und dann auf “Checkin pending changes” - jetzt ist er wieder weg :)
image

Schritt 5: Fertig

Wenn wir alles richtig gemacht haben, sieht es so aus:

image

Ich muss allerdings zugeben, dass die Solution-Folder nicht sein müssen, wichtig ist erstmal, dass es auf dem Server auch richtig hinterlegt ist:

image

Abschließender Hinweis:

Das ist natürlich nur so einfach bei einem neuen Projekt - alte Projekte in diese Struktur packen ist sicherlich nicht ganz so leicht, aber auf der TFS Guide Seite findet man noch sehr viele Informationen darüber.

Falls irgendwas mit meinen Ratschlägen nicht stimmt, dann einfach Feedback geben :)

ShareThis

HowToCode "ReadYou": ToDo Liste managen

10.06.2008 22:56:56 | Robert Mühsig

Gestern habe ich bei Codeplex ein “ReadYou” Projekt angelegt.
Info am Rande: Die “HowTos” sind allgemeiner gehalten - in der Kategorie “HowToCode” dreht es sich um zusammengesetze spezielle Sachen, wobei ich hier “HowTos” etc. verlinke - so ist jedenfalls momentan meine Vorstellung von diesen beiden Kategorien ;)

Um einen Überblick über die verschiedenen Tätigkeiten bei “ReadYou” zu behalten muss unbedingt eine ToDo Liste her.
Info am Rande: Ich dokumentiere dies Schritt für Schritt - sodass ich dies evtl. später auch mal unseren Azubis vermachen kann, daher auch der “low-level” Einstieg ;)

image

Ob das Feature auf Codeplex genau so genutzt wird, weiß ich natürlich nicht - da am Ende allerdings ein Team Foundation Server steckt und dort solche ToDos sich (mehr oder weniger komfortabel) als WorkItem wiedergeben lassen, werde ich dies hier auch machen :)

Codeplex: “Issue Tracker”

Mit dem Tracker kann man sehr leicht WorkItems/Issues/Features anlegen.

image

Man kann dazu viele Details mit hinzufügen:

image

Komponenten - Grobstruktur

Codeplex bietet nettes kleines Feld namens “Component”:

image

Hier können wir unsere Applikation erst mal grob strukturieren:

image

Genau diese Komponenten legen wir an:

image

Visual Studio 2008: Team Explorer Integration

Da ich natürlich nicht immer auf die Codeplex Seite die neuen ToDos anlegen möchte, geht dies auch mit dem Team Explorer. Hier sehen wir die selben Input-Felder wie auf der Website (diese Eingabemaske ist vom TFS Projekt Template abhängig) :

image

Visual Studio 2008: ToDos erstellen

Im ersten Schritt habe ich erstmal meine nächsten Schritte als WorkItem beschrieben:

image

Wie zu sehen ist, soll erstmal eine Ordnerstruktur her und wir widmen uns gleich zu beginn einem Kernelement: Das User System.

Was haben wir hier gelernt?

Überblick über ein Projekt zu behalten ist nicht ganz einfach - ein ToDo Zettel ist ganz nett, allerdings werden wir später noch herausfinden, was bei diesen WorkItems noch schick ist und warum es sich lohnt, so eine Art Kreislauf einzuhalten:

image

Schritt 1: Anforderung kommt rein
Ein Kunde (in dem Fall ich) möchte ein bestimmtes Features, z.B. sollen sich User anmelden können usw. (siehe hier).

Schritt 2: Bewerten / Aufwand abschätzen
Der Schritt ist etwas “schwammig” bei mir hier - normalerweise sollte der Entwickler an dieser stelle bereits eine ungefähre Ahnung haben, was da eigentlich gewollt ist und auch eine Ahnung haben, wie lange dies oder jenes dauert. Hier kann auch eine Konzeptionsphase sein (oder sogar sollte ;) )

Schritt 3: In entsprechende WorkItem umwandeln
Wenn das soweit ok geht (und vom Projektleiter abgesegnet wurde etc.) müssen wir die schwammigen Featurebeschreibungen in konkrete ToDos umwandeln.
Es kann auch sein, dass hier gar kein WorkItem abfällt, weil es z.B. kein Code zu schreiben gibt und auch sonst eher eine “administrative Aufgabe” ist.

Schritt 4: Implementieren
Da wir hier nach Test-Driven-Development vorgehen, wird während dieser Phase bereits ausgiebig getestet - jedenfalls wird hier alles eingebaut, was nötig ist um das zu erfüllen was gefordert ist.

Schritt 5: Testen
Hier sollte alles nochmal überprüft werden - nach dieser Phase geht es live!

Schritt 6: Ausliefern & WorkItem schließen
Wenn alles funktioniert, dass Feature fertig implementiert wurde und auch beim Kunden läuft, können wir hier das WorkItem schließen und können uns erstmal zurücklehnen :)

Da man mit den Unit-Tests in Zusammenhang mit WorkItems was schickes machen kann, wollte ich dies nochmal hervorheben.

Im nächsten Blogpost gibt es also das erste mal direkten Code zu sehen - jedenfalls werden wir das Visual Studio nicht nur zum Managen nehmen, sondern auch zu unserer eigentlichen Aufgabe zurückkehren: Coden!

ShareThis

Digital Lifestyle im Auto

10.06.2008 10:07:00 | Lori Grosland

Wie sieht das Digital Lifestyle im Auto aus? Letzte Woche habe ich mit Oliver Riekenbrauk darüber gesprochen.  Er ist Marketing Manager für die Microsoft Automotive Business Group in Europa und hat mir ein Fiat Bravo gezeigt, das mit Fiats Blue&Me-System ausgerüstet ist.  Blue&Me basierest auf einem Microsoft Technologie und ist ein leicht-zu-bedienendes Infotainment-System mit einem Bluetooth-System fürs Handy im Auto, ein USB-Port für den Anschluss eines MP3-Players und alles (Handy, MP3-Player und Navigationssystem) kann per Sprachbefehl gesteuert werden. 

Auch ein Vorteil zu einem USB-Port im Auto, ist das das System kann einfach upgedatet werden.  Jeden Tag gibt es neue Telefonen, PDAs und MP3-Playern und über diesen USB-Port kann Blue&Me nachträglich mit diesen Geräten upgegradet werden.

Blue&Me ist in Europa momentan nur in Fiat, Alfa Romeo, Lancia und IVECO-Modellen zu haben.  In Nordamerika benutzt Ford das Microsoft Auto Softwareplattform in einem Produkt namens Sync.  In die Zukunft wird es weitere Automarken mit so einem Infotainment-System geben.

In diesem Video Oliver zeigt mir wie benutzerfreundlich und unkompliziert das Blue&Me System ist.

Und wieder gibt es Bücher zu gewinnen...

10.06.2008 08:20:25 | Jürgen Gutsch

Smashing Magazine hat wieder einmal eine Aktion gestartet, in der Bücher zum Thema CSS und Webdesign abgestaubt werden können.

Wie es funktioniert und an welche Bücher es zu gewinnen gibt kann hier nachgelesen werden:
http://www.smashingmagazine.com/2008/06/09/books-giveaway-comment-and-win/

HowTo: Codeplex Projekt anlegen

09.06.2008 23:33:26 | Robert Mühsig

Für das Community-Projekt “ReadYou” habe ich nun ein Codeplex Projekt angelegt. Ich werde demnächst ein größeren ReadYou Post bringen - dies ist zur Vorbereitung :)
Das HowTo soll nur Anlaufinformationen zu dem ohnehin einfachen Registrierprozess darstellen (falls jemand anderes auch das Interesse hegt, bei Codeplex ein Projekt anzulegen) :

Nachdem wir angemeldet sind, können wir einfach auf der rechten Seite ein neues Projekt anlegen:

image

Die wichtigsten Daten angeben:

image

Den Codeplex User Agreements noch zustimmen:

image

Und da ist unser Projekt:

image

Nächster Schritt (bzw. hat man Zeit bis zum Releasen) :

Der Wahl einer Softwarelizenz. Es gibt bei Codeplex einige Open Source Lizenzen zur Auswahl, darunter die GPL, Apache Licence oder auch die MS-PL. Welche die richtige ist, ist momentan für mich auch ein Rätsel ;)

Frist: 30 Tage bis zum Löschen!

Wie bereits oben in dem Screenshot zu sehen ist, gibt es beim Anlegen eine Deadline von 30 Tagen - in dieser Zeit muss das Projekt gepublished werden (ist das überhaupt ein Wort? ;) ) - keiner mag leere Projekte, daher dieser Filter.

Wie verbinde ich mich nun mit dem Projekt?

Ich werde den TFS Team Explorer nehmen, es gibt allerdings noch andere Clients.

Interessant: Continuous Integration mit CruiseControl.NET ist es mit Codeplex möglich - siehe hier. Vielleicht ist das für später ganz interessant.

Die eigentlichen Verbindungsdetails lassen sich über den Reiter “Source Control” abrufen:

image

Das ganze tippen wir mal in unser Visual Studio über den Team Explorer ein:

image

Und siehe da - unser leeres Projekt:

image

Jetzt können wir anfangen Work Items/Source Code hochzuladen - wie genau und was da die Best Practices sind, versuche ich in ein paar HowTos bzw. einem größeren ReadYou Post zu veranschaulichen.

Weitere Informationen findet man auf der Codeplex Seite selbst.

ShareThis

Wöchentliche Rundablage: Silverlight 2, WPF, ASP.NET MVC, jQuery…

09.06.2008 21:08:53 | Robert Mühsig

Silverlight 2:

WPF / Windows Presenation Foundation:

ASP.NET / ASP.NET MVC:

jQuery:

.NET Framework:

LINQ:

Tools:

TDD:

Pattern:

WCF:

CSS / Icons:

ShareThis

Silverlight 2 Beta 2 & Expression Blend 2.5 June 2008 Preview released

08.06.2008 19:02:25 | Robert Mühsig

Für fleissige Silverlight-Menschen gibt es neues Futter:

Silverlight 2 Beta 2 (@ ScottGus Blog)

und eine neue Blend 2.5 Version (Downloadlink) wurde released :)

ShareThis

LAN-Scan, aber richtig schnell! Teil - 1

08.06.2008 17:25:41 | Klaus Bock

Im vorherigen Artikel (To parallel or not to parallel) habe ich eine Aufgabe gezeigt die sich, durch den intensiven Zugriff auf die Festplatte, nicht sonderlich gut zur Parallelisierung eignet. In diesem Artikel will ich eine Methode zeigen wie trotz intensiver E/A-Zugriffe, allerdings auf die Netzwerkkarte, sehr gut Parallelisiert werden kann. Gerade das "pingen" von ganzen LAN-Segmenten lässt sich gut Parallelisieren, da der Thread welcher den Ping ausführt solange wartet bis er Antwort erhält oder der Timeout abgelaufen ist. Während der Thread, oder Ping, auf Antwort wartet kann der nächste Ping schon wieder abgesetzt werden. Hier gilt es die ideale Anzahl von gleichzeitig initialisierten Ping-Objekten zu bestimmen. Während verschiedener Tests habe ich den Wert von 36 Thread's pro Kern als besten ermittelt. Für jene unter euch die's nicht glauben ist hier ein ScreenShot mit der Zeit:

netScan elapsed

Und als Nachweis dass die Threads auch wirklich etwas tun, außer Thread.Sleep(), hier ein ScreenShot mit dem Ergebnis:

netScan ClientList

Wie aus dem ScreenShot ersichtlich wird der Hostnamen aufgelöst und der Online-Status ermittelt. Ich finde ein ganzes LAN-Segment mit 254 möglichen Adressen in rund 2 Sekunden zu überprüfen und auszuwerten ist richtig schnell, oder etwa nicht?

Ich verwende dieses mal nicht die Parallel-Klasse, sondern die Task- und TaskManager-Klasse aus der TPL. Die TaskManager-Klasse bietet durch Verwendung einer TaskManagerPolicy die Möglichkeit, sehr genau das Verhalten der Thread-Erzeugung und -Verwendung zu steuern. In dieser Policy wird festgelegt:

  • Die minimale Anzahl der CPU's die verwendet werden soll.
  • Die maximale Anzahl der CPU's die verwendet werden soll.
  • Die ideale Anzahl an Thread's pro CPU, die verwendet werden soll.
  • Die maximale Größe des Stack.
  • Die Priorität der verwendeten Thread's.

Klingt ja ganz nett. Doch ich wollte wissen, ob die Policy tatsächlich auch so umgesetzt wird. Also habe ich mir eine Methode geschrieben um das zu überprüfen. In dieser Methode wird die verwaltete ID des jeweiligen Thread ermittelt und in eine Hashtable eingefügt, wenn die ID noch nicht in dieser vorhanden ist. Hier der Code der Test-Methode:

[SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlThread)]
private static void SimulateCheck(object obj)
{
    // ThreadAffinität wird verwendet um das gleiche Verhalten
    // wie in der OriginalMethode zu simulieren
    Thread.BeginThreadAffinity();

    // die managed ThreadId holen
    int tId = Thread.CurrentThread.ManagedThreadId;

    // prüfen ob die ThreadID bereits in der Tabelle vorhanden ist.
    if (!hashTable.ContainsKey(tId))
    {
        // nicht vorhanden, jetzt hinzufügen
        hashTable.Add(tId, string.Empty);
    }

    // Pause, in etwa so lange wie die Ping-Verarbeitung
    // in der OriginalMethode benötigt.
    Thread.Sleep(600);

    // ThreadAffinität beenden.
    Thread.EndThreadAffinity();
}

Die TaskManagerPolicy soll also gewährleisten das bei einer Anzahl von 36 Thread's, als idealThreadCount, auf einem DualCore System nie mehr als 72 Thread's erzeugt und verwendet werden. In der Hauptmethode wird eine Liste von 253 Thread,s erzeugt und abgearbeitet. Anschließend wird diese Liste durchlaufen und jeder Thread auf seine Beendigung überprüft. Nachfolgend der verwendetet Code in der Hauptmethode:

static void Main(string[] args)
{
    //ipHelper = new IPHelper(IPHelper.GetLocalIPAdress(false));
    netSegment = "192.168.1."; // ipHelper.BaseAdress;

    // ClientList initialisieren
    clientList = new ClientList();

    hashTable = new Hashtable();

    // neue Stopwatch-Instanz initialisieren
    Stopwatch sw = new Stopwatch();

    #region using Tasks

    // Initialisiert eine neue TaskManager-Instanz mit der Policy
    // nutze min. 1 CPU,
    // nutze max. alle CPU's,
    // ideale Anzahl der Threads (ermittelt 36)
    // maxStackSize Default,
    // ThreadPriorität grösser als Normal
    TaskManager manager = new TaskManager(
        new TaskManagerPolicy(
                1,
                Environment.ProcessorCount,
                threadCount,
                0,
                ThreadPriority.AboveNormal));

    Console.WriteLine("starte parallele Überprüfung. Verwende PFX Tasks.");
    sw.Start();

    // generische Liste vom Typ Task
    // mit 253 Elementen initialisieren
    List<Task> taskList = new List<Task>(253);

    // hostAdress mit 1 initialisieren
    int hostAdress = 1;

    // true, bis hostAdress 254 erreicht
    for (int i = 0; hostAdress < 255; i++)
    {
        // eine neue Instanz der Task-Klasse mit TaskManager
        task = Task.Create(
                        SimulateCheck,
                        hostAdress,
                        manager,
                        TaskCreationOptions.None);

        // den Task zur Taskliste hinzufügen.
        // der Task wird sofort gestartet
        taskList.Add(task);

        // hostAdress erhöhen
        hostAdress++;
    }

    // durch die taskList iterieren um zu warten bis alle
    // Tasks beendet sind.
    foreach (Task task in taskList)
    {
        task.Wait();

        // prüfen ob der Task beendet ist
        if (task.IsCompleted)
        {
            // das Task-Objekt entsorgen
            task.Dispose();
        }
    }

    // TaskManager entsorgen
    manager.Dispose();

    // benötigte Zeit abfragen
    elapsedTime = sw.Elapsed;
    sw.Reset();
    Console.WriteLine("Benötigte Zeit: " + elapsedTime.ToString());
    Console.WriteLine();

    #endregion

    // StringBuilder wird verwendet um das Ergebnis in einer Reihe auszugeben
    StringBuilder sb = new StringBuilder(hashTable.Count);

    Console.WriteLine("Thread-Verwendung:");
    Console.WriteLine(string.Format("Anzahl der Threads erwartet {0}",
        threadCount * Environment.ProcessorCount));
    Console.WriteLine("Anzahl der verwendeten Threads: " + hashTable.Count);
    Console.WriteLine();
    Console.WriteLine("Verwendete ThreadIDs:");

    foreach (DictionaryEntry de in hashTable)
    {
        sb.Append(de.Key.ToString()).Append(", ");
    }

    // letztes Komma und Leerzeichen entfernen
    Console.WriteLine(sb.ToString().Trim(',', ' '));

    Console.WriteLine();
    Console.WriteLine("Zum beenden eine Taste drücken.");
    Console.ReadKey();
}

Der untere Bereich dient nur zur Ausgabe der Tests in der Konsole. Hier die Ausgabe der obigen Methode:

netScan Console

Tatsächlich wird, wie im obigen ScreenShot zu sehen, die TaskManagerPolicy umgesetzt und nur 72 Thread's werden verwendet obwohl 253 Task's erzeugt werden.

Tatsächlich ist das wirklich nützliche an diesem Verfahren, dass je nach Anwendungsfall die Anzahl der verwendetet Thread's gesteuert werden kann. In einer Windows-Anwendung kann die Anzahl hoch gesetzt werden, da die Anwendung mit Sicherheit im Vordergrund läuft und der Anwender auf das Ergebnis wartet. Soll die Lösung in einem Dienst Verwendung finden, kann sowohl die Anzahl als auch die Priorität der erzeugten Thread's dem entsprechend niedrig gewählt werden. Denkbar ist auch eine variable Größe der Thread-Anzahl und -Priorität, je nach momentaner Auslastung des Systems. Gerade in der Verwendung als Dienst könnte das eine sauber Lösung sein um die Ressourcen des Systems nicht über Gebühr zu belasten.

Soweit zum Grundgerüst und dem Test der parallelen Verarbeitung. Das nächste mal werde ich auf den eigentlichen LAN-Scan und das threadsichere einfügen bzw. ersetzen des Ping-Ergebnis in der ClientList-Klasse eingehen.

To parallel or not to parallel?

07.06.2008 13:40:59 | Klaus Bock

Da neuere Computer kaum noch mit Prozessoren mit nur einem Kern bestückt werden, wird sich früher oder später ein jeder einmal Gedanken über die Parallelisierung seiner Anwendungen machen müssen. Die Parallel Extension to the .NET Framework 3.5, derzeit als zweite CTP vorliegend, bietet einen relativ einfachen Einstieg in die Parallelisierung von rechenintensiven Aufgaben. Interessierte können die Juni CTP hier herunter laden.

Wie überall gibt aus beim parallelisieren Aufgaben die gut und solche die weniger gut geeignet sind. Methoden die sehr E/A-intensive Aufgaben erledigen sollen, eignen sich eher nicht so gut zur Parallelisierung, da jeder Thread so lange blockiert wird, bis der Zugriff auf die jeweilige Ressource wieder frei gegeben ist. Gerade aus diesem Grund habe ich ein Beispiel gewählt das genau solch eine Aufgabe erledigen soll, um die Leistungsfähigkeit der TPL (Task Parallel Library) zu zeigen. In der gestellten Aufgabe gilt es alle vorhandenen Assemblies im GAC aufzulisten und die zugehörigen Installationsreferenzen zu ermitteln. Sowohl das Auflisten der Assemblies als auch das Ermitteln der Referenzen erfolgt mit Hilfe der Fusion-API. Um überhaupt eine Größe zu haben mit der ich vergleichen konnte, habe ich die einzige mir bekannte Anwendung verwendet welche auch die Fusion-API nutzt; die gacutil.exe aus dem SDK. Ich hoffe hier nicht gegen Punkt 2 der Eula, welcher Vergleichsstests regelt, des .NET Framework zu verstoßen. Zur Ermittlung der Referenz-Zeit habe ich gacutil.exe mit dem Argument -lr in einem Prozess gestartet und mit der Stopwatch-Klasse die benötigte Zeit ermittelt. hier der Code:

Process proc = new Process();
proc.StartInfo.FileName = @"C:\Programme\Microsoft SDKs\Windows\v6.0A\bin\gacutil.exe";
proc.StartInfo.Arguments = "-lr";

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();

proc.Start();
proc.WaitForExit();

stopWatch.Stop();
TimeSpan elapsed = TimeSpan.FromMilliseconds((double)stopWatch.ElapsedMilliseconds);

Diese Methode habe ich fünf mal ausgeführt. Dabei ergab sich, auf meiner Maschine, ein Mittelwert von 55,638 Sekunden für 676 Elemente. Somit war die Referenz-Zeit ermittelt als auch die Anzahl der Elemente die gefunden werden mussten.

Als Container zum aufnehmen der gefundenen Daten, habe ich mich für eine NameValueCollection entschieden. Um das, pro Iteration, gefundene Name-Wert Paar in die Kollektion einzufügen, habe ich zwei verschiedene Wege getestet. Als ersten das direkte Anfügen des Schlüssel-Wert Paares als Zeichenfolgen mit der NameValueCollection.Add(String, String)-Methode. Als zweiten Weg  das Erzeugen einer zweiten NameValueCollection und diese mit der NameValueCollection.Add(NameValueCollection)-Methode in die Haupt-Kollektion einzufügen. Da die statischen for und foreach Methoden der TPL mit Delegaten arbeiten, habe ich zwei Hilfsmethoden erstellt um diese als Delegaten zu nutzen. Folgend die beiden Hilfsmethoden AddToCollection(String) und GetNameValuePair(String).

Die Methode AddToCollection(String) ist vom Typ void und fügt das Schlüssel-Wert Paar als Zeichenfolge an eine, als Class Member instanziierte, NameValueCollection an.

private static void AddToCollection(string assemblyName)
{
    AssemblyCacheInstallReferenceEnum instRefEnum =
        new AssemblyCacheInstallReferenceEnum(assemblyName);
    InstallReference instRef = instRefEnum.NextReference;

    // prüfen ob eine InstallReferenz-Instanz übergeben wurde
    if (instRef != null)
    {
        StringBuilder sb = new StringBuilder(255);
        sb.AppendFormat(
                CultureInfo.CurrentCulture,
                Resources.AsmRefSchemaMsg,
                InstallReferenceGuid.GetGuidSchemeName(instRef.GuidScheme));
        sb.Append("  ");
        sb.AppendFormat(
                CultureInfo.CurrentCulture,
                Resources.AsmRefIDMsg,
                instRef.Identifier);
        sb.Append("  ");
        sb.AppendFormat(
                CultureInfo.CurrentCulture,
                Resources.AsmRefDescriptionMsg,
                instRef.NonCanonicalData);
        assemblyCollection.Add(assemblyName, sb.ToString());
    }
    else
    {
        // keine InstallReferenz gefunden, NULL anfügen
        assemblyCollection.Add(assemblyName, null);
    }
}

Die Methode GetNameValuePair(String) ist vom Typ NameValueCollection. Diese zurückgegebene Kollektion wird an eine, in der aufrufenden Methode instanziierte, Kollektion angehängt.

private static NameValueCollection GetNameValuePair(string assemblyName)
{
    NameValueCollection collection = new NameValueCollection(1);
    AssemblyCacheInstallReferenceEnum instRefEnum =
                       new AssemblyCacheInstallReferenceEnum(assemblyName);
    InstallReference instRef = instRefEnum.NextReference;

    // prüfen ob eine InstallReferenz-Instanz übergeben wurde
    if (instRef != null)
    {
        StringBuilder sb = new StringBuilder(255);
        sb.AppendFormat(
                CultureInfo.CurrentCulture,
                Resources.AsmRefSchemaMsg,
                InstallReferenceGuid.GetGuidSchemeName(instRef.GuidScheme));
        sb.Append("  ");
        sb.AppendFormat(
                CultureInfo.CurrentCulture,
                Resources.AsmRefIDMsg,
                instRef.Identifier);
        sb.Append("  ");
        sb.AppendFormat(
                CultureInfo.CurrentCulture,
                Resources.AsmRefDescriptionMsg,
                instRef.NonCanonicalData);
        collection.Add(assemblyName, sb.ToString());
    }
    else
    {
        // keine InstallReferenz gefunden, NULL anfügen
        collection.Add(assemblyName, null);
    }

    return collection;
}

Als Container welcher die Namen der Assemblies im GAC enthält, wird eine ReadOnlyCollection vom Typ string verwendet. Da die Erzeugung einer solche Kollektion bereits in der Klasse als öffentliche Methode vorhanden ist, wird diese einfach benutzt um eine Aufzählung der benötigten Assemblies zu erhalten. Nun braucht nur noch durch diese Aufzählung iteriert zu werden, und der jeweilige Rückgabewert wird an eine der beiden Methoden übergeben.

Um zu ermitteln ob die parallele Methode überhaupt einen Vorteil bringt, muss zunächst einmal die benötigte Zeit der sequenziellen Verwendung ermittelt werden. Wie schon für gacutil.exe wurde auch hier der Mittelwert aus fünf Durchgängen ermittelt. Wie bereits oben beschrieben ist dies nur eine einfache Iteration. Als einfachste Möglichkeit bietet sich hier eine foreach Schleife an.

// Collection mit Assemblies füllen die mit
// assemblyName übereinstimmen
assemblyList = GetAssemblies(assemblyName);

foreach (string asmName in assemblyList)
{
    // je nach verwendeter Methode auskommentieren

    //AddToCollection(asmName);
    assemblyCollection.Add(GetNameValuePair(asmName));
}

Als Mittelwerte habe ich für die Methode AddToCollection(String) 43,338 Sekunden und für die Methode GetNameValuePair(String) 43,678 Sekunden ermittelt. Für 676 angefügte Elemente ist die Differenz von ca. 0,3 Sekunden zu vernachlässigen. Es zeigte mir aber auch, dass es faktisch keinen Unterschied bedeutet ob nun das Schlüssel-Wert Paar als zwei Zeichenfolgen oder als NameValueCollection angefügt werden.

Vor dem Erscheinen der Juni 2008 CTP des ParallelFX hatte ich bereits die Dezember 2007 CTP der selbigen genutzt. Ich wollte natürlich wissen ob sich in Sachen Performance etwas getan hatte. Also habe ich als erstes die Dezember 2007 CTP zum Testen der Parallelisierung verwendet. Als Container für die Assemblynamen wurde die gleiche ReadOnlyCollection wie in der sequenziellen Methode verwendet. Zum parallelen iterieren verwende ich die statische foreach Methode der Parallel Klasse. Hier das Code-Konstrukt der verwendeten Methode:

// Collection mit den Namen aller Assemblies im GAC erzeugen
assemblyList = GetAssemblies(null);

var exceptions = new ConcurrentStack<Exception>();

Parallel.ForEach<string>(
    assemblyList,
    delegate(string asmName)
    {
        try
        {
            // je nach verwendeter Methode auskommentieren

            //AddToCollection(asmName);
            assemblyCollection.Add(GetNameValuePair(asmName));
        }
        // zum testen werden die Ausnahmen nicht unterschieden
        catch (Exception ex)
        {
            exceptions.Push(ex);
        }
    });

// prüfen ob Ausnahmen abgefangen wurden
if (!exceptions.IsEmpty)
{
    throw new AggregateException(exceptions);
}

Die Verwendung der Parallel-Klasse aus der Dezember 2007 CTP ergab für die Methode AddToCollection(String) einen Mittelwert von 39,316 Sekunden und für die Methode GetNameValuePair(String) 38,495. Beide Methoden brachten einen Geschwindigkeitsvorteil von rund 4 Sekunden. Wie ich jedoch Eingangs darauf hingewiesen habe, eignen sich Aufgaben mit intensiven E/A-Operationen nicht besonders zur Parallelisierung.

Um zu sehen was sich in der Juni 2008 CTP des ParallelFX getan hatte, wiederholte ich den Test mit der Parallel-Klasse aus der neueren CTP mit folgendem Ergebnis: die Methode AddToCollection(String) benötigte nur noch 36,495 Sekunden im Mittel. Auch die zweite Methode GetNameValuePair(String) war mit 36,662 im Mittel deutlich schneller geworden. Aus den anfänglichen rund 4 Sekunden Differenz zur sequenziellen Verarbeitung waren mittlerweile rund 7 Sekunden geworden. Bei rund 43,5 Sekunden als Basis ergeben die rund 36,5 Sekunden doch einen Vorteil von rund 16%.

Ich habe auf einen direkten Vergleich mit dem Ergebnis der gacutil.exe bewusst verzichtet da sie weder in verwaltetem Code erstellt wurde, noch ich irgendwelche Kenntnisse über die Implementierung der Fusion-API in der gacutil.exe besitze. Ich habe die Kennwerte der gacutil.exe lediglich ermittelt um einen Wert zu haben an dem ich mich orientieren kann und um die Anzahl der zu erwartenden Elemente zu kennen. Was hilft die beste Methode, wenn sie falsche Ergebnisse, in meinem Fall die Anzahl der Assemblies, liefert.

In der Zusammenfassung ergibt sich für mich folgendes Bild:

  • Als Aufgabe eine Operation die sich nur sehr bedingt zur Parallelisierung eignet.
  • Ohne nennenswerten Mehraufwand eine parallele Lösung gefunden.
  • Einen Geschwindigkeitsvorteil von rund 16% erzielt, mit einer CTP welche mit Sicherheit noch nicht optimiert ist.

Ob nun in Zukunft vermehrt von der Parallelisierung Gebrauch gemacht wird? Ich weis es nicht. Von meiner Warte aus kann ich nur sagen: Mit dem Parallel-Erweiterungen hat uns das ParallelFX-Team ein einfach zu handhabendes und sehr wirkungsvolles Werkzeug zur Verfügung gestellt. Falls jemand einmal Probleme mit der Parallel-Bibliothek haben sollte helfen die Mitglieder des Teams gerne im MSDN-Forum. Ich kann aus eigener Erfahrung sagen, dass mir dort schnell und kompetent geholfen wurde.

Ob sich nun der einzelne mehr mit der Parallelisierung seiner Aufgaben auseinandersetzt bleibt ihm selbst überlassen. Ich werde es auf jeden Fall tun.

 

HowToCode "YouRead": …eigentlich heisst es "ReadYou"

06.06.2008 19:11:02 | Robert Mühsig

Da hat sich wohl der Fehlerteufel eingeschlichen - in den letzten beiden Blogposts war immer von “YouRead” die Rede, dabei heisst es eigentlich (jedenfalls ist diese Domain jetzt dafür gedacht ;)ReadYou“.

Spricht sich auch deutlich einfacher :) (und der Titel war auch bereits bei dem UI Prototyp zu sehen)

Der Fehler wurde mir bewusst, als ich voller Stolz meiner Freundin berichtet habe, dass ich nun mit “YouRead” angefangen hab.
Naja, Namen sind am Ende auch Schall und Rauch - freuen wir uns also nun auf viele “ReadYou” Blogposts ;)

ShareThis

Social Linkkram

06.06.2008 17:21:22 | Albert Weinert

Ich habe auf der rechten Seite mal ein paar Buttons mit links zu Social Networks (On- und Offline) hinzugefügt wo ich mehr oder weniger aktiv bin.

HowToCode "ReadYou": Was soll das System denn leisten? - Gedanken an die Anforderungen

05.06.2008 23:04:51 | Robert Mühsig

In dem letzten “ReadYou” Post ging es allgemein um den Gesamtplan - als erstes möchte ich nochmal komplett ohne Code arbeiten und auch keine großen Gedanken zur Architektur machen.

Hier geht es um die Anforderungen die ich an das System habe. Neben den bereits gestellten Qualitätsanspruchen (Source Control, Tests, Dokumentation) sollten auch gewisse Grundfunktionen enthalten sein.

Wichtiger Hinweis: Ich werde die Anforderungen bewusst “simpel” fassen - im professionellen Umfeld sollte an dieser Stelle besonders viel mit dem Kunden geredet werden um herauszufinden, was er eigentlich will!

image

image

Was genau muss die Software/Plattform leisten?

Bilder sagen manchmal mehr als tausend Worte (und vor allem empfinde ich es dann meist viel logischer, wie das hinterher aussehen muss):

image

Aufmerksamen Lesern des Blogs sollte dieses Bild aus dem UI Prototyping mit Powerpoint bekannt vorkommen.

Ich liste jetzt mal alle Funktionen auf:

  • User-Bereich:
    • Login/Logout/Register/Password-Recovery
    • Admin hat Management-Oberfläche
    • User kann Profildaten speichern
    • User sollte sich auch mit Open ID, Windows Live ID etc. anmelden können
    • User kann andere User suchen etc.
    • User kann Bücher anlegen (später evtl. auch Blogs)
    • “Meine Seite” ähnlich wie StudiVZ Home
    • Community - “Gruppen” etc.
  • Bücher-Bereich:
    • Anlegen / Löschen / Managen
    • Bewerten
    • Taggen
    • Kategorisierung der Bücher
    • Amazon-Anbindung
    • Rezessionen verfassen (oder man nimmt die Amazon-Rezessionen über die API ;) )
  • Allgemeines:
    • Aktive User ähnlich wie in den ASP/MSDN Foren
    • Neuste Bücher wie bei YouTube “Videos right watched now…”

Um es kurz zu sagen: Typische Communityseite - wobei ich die Anforderungen hier nicht so eng fasse.

Eine interessante Sachen die ich gerne einbauen würde:

  • Providermodell für Authentifizierung & Userdaten

Das ASP.NET Membership-System gefällt mir nicht wirklich - es ist nett, hat aber seine Tücken und ist bei manchen Sachen (Profiles z.B.) nicht wirklich schön.
Zudem möchte ich die Authentifzierung von den eigentlichen Nutzerdatenzugriff trennen, weil ich bislang häufig bei Projekten folgendes Szenario hab:

  • Oracle-DBs mit wilden Benutzerdaten
  • AD / LDAP als Authentifizierung, allerdings stammen die wirklichen Userdaten dann aus einer anderen DB
  • Profildaten etc. werden extra gespeichert

Insbesondere durch die Anforderung, die ich mir selbst gestellt habe, als Authentifizerung auch OpenID, Windows Live etc. zu unterstützen, wären die Userdaten und die Userauthentifizierung sowieso getrennt.

“AuthenticationRepository” & “UserRepository”:

Ich bin sehr angetan von Rob Conerys Architektur mit den Pipes & Filters Modell und dem IQueryable Interface.

Als momentane Idee steht daher solch eine “Grobe”-Architektur:

image

Diese “Architekturgedanken” sind hier für die Erklärung der Anforderung zu verstehen. Code etc. wird es erst später geben ;)

Data:
Hier versteh ich die verschiedenen Datenquellen - z.B. eine Oracle-DB, eine SQL-DB, Windows Live oder OpenID (wenn es um die Authentifizierung geht).

Repository;
Hier ist die Datenzugriffsschicht - als wäre hier angenommen LINQ to SQL/ADO.NET EF/NHibernate/SubSonic oder die Windows Live API etc. einzubauen.

Service:
Der Service sollte so lose wie möglich an das “Backend” gekoppelt sein - ich denke dies hier ist ein nettes Einsatzgebiet von Dependency Injection. Dieser Serivce wird im Frontent aufgerufen und gibt das an die entsprechenden Repositories weiter.

Mein Wunschtraum: 

Der Idealzustand dieser Plattform wäre, wenn ich an einer Codestelle oder über Konfiguration mein “Datenquellen” angeben kann, z.B.:

Variante A)
Nimm als Authentifizierungsquelle und Userquelle den SQL Server.

Variante B)
Nimm als Authentifizierungsquelle das Active-Directory und als Userquelle die Oracle DB

Am Ende muss man “nur” einen entsprechenden Provider bereitstellen und ich will Service nichts großes ändern müssen - ob das so klappt, wage ich jetzt mal zu bezweifeln, da die Anmeldedaten auch unterschiedlich sind - aber das wird eine interessante Sache für die nächsten Blogposts ;)

Alles klar?
Kunde: “Falls Sie noch Fragen haben, melden Sie sich einfach. Ich denke, dass ist eine machbare kleine Anwendung - das dauert bestimmt nicht lange.”

Programmierer:

image

Feedback:
Auch wenn ich einen möglichst “professionellen” Ansatz verfolgen möchte, will ich hier möglichst bald euch Code präsentieren. Die Rubrik heisst ja auch “HowToCode” und nicht “HowToRequirementsManagement”, daher sei mir hoffentlich die spärlichen Anforderungen verziehen ;)

Aber wenn ihr noch Vorschläge habt (was unbedingt mit rein müsste, und was man mit bedenken sollte), dann immer her damit.

Ausblick:
Die Idee mit der Authentifizierung/Userdata-Sache werde ich sicherlich erstmal prototyisch das nächste mal in Angriff nehmen und genauer auf die anderen Architekturpunkte eingehen.

ShareThis

Was macht eigentlich so eine T-Systems MMS?

04.06.2008 22:13:47 | Robert Mühsig

Vor kurzem hat Oli ja bereits von unseren Mitarbeitergesuch geschrieben. Wer sich nun fragt, was wir eigentlich im Groben den ganzen Tag machen, dann schaut euch einfach mal das Video an:

… das und natürlich noch viel mehr :)

Ein nettes (Promo-) Video :)

Weitere Videos findet man auf dem T-Systems MMS YouTube Channel.

ShareThis

HowTo: Generisches speichern und laden von XML Dateien bzw. wozu sind Generics gut?

04.06.2008 11:05:58 | Oliver Guhr

Immmer wenn ich eine Xml Datei lesen oder schreiben will fange ich wieder an das Code-Snippet dafür zu suchen und an meine Objekttypen anzupassen. Also habe ich mir jetzt mal eine generische Version geschrieben. Das T steht dabei für den noch unbekannten Typ der erst zur Laufzeit übergeben wir.

public static void Save<T>(String path, T obj)
        {
            XmlSerializer Serializer = new XmlSerializer(typeof(T));
            FileStream Stream = new FileStream(path, FileMode.Create);
            Serializer.Serialize(Stream, obj);
            Stream.Close();
        }

        public static T Load<T>(String path)
        {
            XmlSerializer Serializer = new XmlSerializer(typeof(T));
            StreamReader Stream = new StreamReader(path);
            T myObject = (T)Serializer.Deserialize(Stream);
            Stream.Close();
            return myObject;
        }

Der Zugriff erfolgt so:

// Daten
            String TestString = "Hallo Xml Welt";
            //schreiben..
            Save<String>("C:\\test.xml", TestString);
            //und lesen
            Debug.WriteLine(Load<String>("C:\\test.xml"));

46 Tutorials zum Thema ASP.NET AJAX Control Toolkit

04.06.2008 08:28:00 | Jürgen Gutsch

Wer sich in das ASP.NET AJAX Control Toolkit einarbeiten möchte kann sich auf www.asp.net jetzt 46 neue Tutorials zu dem Thema ansehen. Alle Tutorials sind sowohl in VB.NET als auch C# vorhanden.

http://www.asp.net/learn/ajax-control-toolkit/

Silverlight 2 Beta 2 kommt diese Woche

04.06.2008 08:14:59 | Oliver Scheer

Heute in der Keynote der Microsoft TechEd kündigten Bill Gates und Soma Somasegar an, dass Silverlight 2 Beta 2 im Verlauf dieser Woche veröffentlicht wird und als kostenloser Download zur Verfügung stehen wird.

Neue Highlights in Silverlight 2 Beta 2:

Optimiertes UI Framework: Beta 2 bringt deutliche Verbesserungen im Bereich Animationen, Fehlerverarbeitung und –reporting, Automatisierung, Barrierefreiheit, Tastaturunterstützung und bei der Performance. Die Kompatibilität zwischen Silverlight und WPF wurde zudem erweitert.

Erweiterte Controls: Silverlight 2 Beta 2 enthält ein neues „Templating Modell“ namens Visual State Manager, mit dem sich komfortabel Templates (Vorlagen) für Controls anlegen lassen. Außerdem neu sind TabControl, Text Wrapping, Scrollbars für Textboxen und neue DataGrid-Funktionen wie Autosize, Reorder, Sort, sowie ebenfalls deutliche Performancegewinne bei Controls. Controls sind nun in die Runtime integriert und müssen nicht mehr mit der Anwendung  ausgeliefert werden.

Verbesserte Netzwerkunterstützung: Zu den neuen Netzwerkfunktionen in Beta 2 gehören domainübergreifender Zugriff und erweiterte Sicherheitsfunktionen, Uploadunterstützung für Webclients und Duplexkommunikation („Push“ vom Server zum Silverlight-Client).

Rich Base Class Library: Beta 2 verfügt über verbesserte Threadingfähigkeiten, LINQ-to-JSON, ADO.NET Data Services, bessere Unterstützung für SOAP, sowie viele weitere Verbesserungen die Networking und Datenverarbeitung deutlich komfortabler und einfacher machen.

Verbessertes Deep Zoom: Beta 2 führt ein neues XML-basiertes Dateiformat für Deep Zoom Image Tiles (Bildkacheln) ein, sowie das neue MultiScaleTileSource  -- somit können bestehende Bild-/Kacheldatenbanken mit Deep Zoom verwendet werden.

Silverlight 2 Beta 2 wird im Laufe der Woche veröffentlicht und kann dann hier herunter geladen werden: http://www.silverlight.net

Außerdem ab sofort verfügbar ist Expression Blend 2.5 June Preview: http://www.microsoft.com/expression/try-it

Silverlight 2 ermöglicht schnelle Vektorgrafiken, flüssige Animationen, Audiointegration, kostengünstige Videopräsentation und Videostreaming bis hin zu HD-Video im Web. Damit lassen sich moderne Benutzeroberflächen und faszinierende Multimediaerlebnisse schaffen, die es Anwendern ermöglichen Videos in HD-Qualität gemeinsam mit Freunden online zu erleben. Ebenso ist es die optimale Plattform, um Businessanwendungen entwickeln, die Geschäftsinformationen dynamisch und plattformunabhängig im Webbrowser visualisieren (eine faszinierende Silverlight-Beispielanwendung die viele der Funktionen von Silverlight demonstriert ist hier verfügbar: http://www.mscui.net/PatientJourneyDemonstrator).

HowToCode "ReadYou": Community-getriebene professionelle Applikationsentwicklung

03.06.2008 22:19:21 | Robert Mühsig

Der Posttitel klingt schon mal recht hochgegriffen und es ist für mich auch eine Art Experiment und hoffe, dass es (früher oder später) auch Feedback gibt ;)

Was ist “ReadYou”?

Trotz des “Web 2.0″-Zeitalters mag ich immer noch Bücher - seien es Fachbücher oder andere Romane. Da ich allerdings langsam den Überblick verliere und es meiner Freundin ähnlich geht, muss eine Plattform her, wo man solche “Bücherbestände” möglichst cool und einfach managen kann (und natürlich die ganzen anderen Community-Sachen die üblich sind).
ReadYou ist daher momentan mein Projektarbeitstitel - am Ende soll unter www.readyou.de eine kleine, schicke Web 2.0 Anwendung stehen.
Das Thema ist jetzt nicht unbedingt jedermanns Sache, allerdings sind meist die “Grundzüge” einer solchen Applikation immer gleich - um ReadYou mal mit markanten Web 2.0 Schlagwörtern zu beschreiben:

image

Ganz guten würden zudem noch Blogs in diesen Kontext reinpassen - aber das ist eine andere Geschichte :)

Aha… was hat das mit uns zutun?

Ich bin ein großer Fan von Rob Conerys MVC Storefront und hab bei seinen Videos einiges gelernt:

Da ich zumeist noch das Problem bei privaten Projekten hab, dass ich irgendwann die Lust verliere, mach ich es diesmal von Anfang an “offen” für jeden - damit ich es nicht irgendwann auf meiner Platte verliere. Dabei soll nicht nur der Source-Code “offen” sein, sondern auch warum ich diese und jene Entscheidung treffe oder warum ich das Tool nehmen möchte.
Es soll eine möglichst professionelle Applikation rauskommen, welche eine möglichst robuste Architektur aufweisst, gut getestet ist und auch (das leidige Thema der Entwickler) sogar dokumentiert ist - also ein möglichst professioneller Ansatz.

Da ich natürlich kein Experte in TDD, Dokumentieren, Toolauswahl, Architektur etc. bin, ist Feedback besonders wichtig :)

Was soll das bringen?

Am Ende soll möglichst eine Art “Leitfaden” entstehen (die Applikation ist quasi das Nebenprodukt), den Anfänger ebenfalls beschreiten können - und auch verstehen, warum diese und jene Entscheidungen getroffen wurden und auch (was sicherlich passieren wird), dass manche Entscheidungen vielleicht unklug waren.
Es sollte nicht als pure deutsche Kopie von Robs Videos zu sehen sein (ich glaub nicht, dass ich so gute Screencasts überhaupt machen könnte), sondern ein Lernexperiment für jedermann - vergleichbar mit einem größeren HowTo - diesmal “HowTo code ReadYou:)

Themen/ToDo´s die ich mir vornehme:

  • Welche Anforderungen hat das System:

Anforderungsmanagement ist ein wichtiger Punkt bei einem Software-Projekt. Da ich hier allerdings selber der Kunde bin, werde ich meine Anforderungen und ungefähren Designvorstellungen selber schreiben - Abschätzungen oder ähnliches werden aber nicht gemacht ;)

  • Architekturgedanken:

Anhand dieser Anforderungen muss eine entsprechende Architektur entworfen werden - dabei werde ich mir sicherlich die eine oder andere Idee bei Rob nochmal genauer anschauen.

  • Source Control & Build:

Als Source Control ist momentan Codeplex meine erste Wahl (TFS Infrastruktur). Hierbei muss ich mal schauen, wie weit man es da treiben kann - Stichwort Continuous Integration). Builds sollten möglichst automatisch ablaufen können - daher werde ich mich an dieser Stelle MSBuild widmen. Hier bin ich mir noch etwas unsicher was Codeplex bietet und wie ich das am cleversten anstelle - aber an dem Punkt bin ich ja noch nicht ;)

  • Implementation & Tests:

Ich versuche möglichst mich nach dem TDD Mantra “Test-First” zu richten - sodass eine möglichst robuste Applikation am Ende rauskommt. Auch Themen wie Dependency Injection, MSTest als Testframework, ASP.NET MVC nehm ich mir vor.

  • Dokumentation:

Da Source-Code möglichst dokumentiert sein sollte, soll auch hier mal von Anfang an daran gedacht werden - ich probier es mal mit Sandcastle (hier & hier gibt es einen guten “Überblick” über die neusten Versionen)

Feedback

Wie bereits oben erwähnt ist Feedback natürlich sehr wichtig - wenn ich aus eurer Sicht was vergessen hab, ihr das Konzept blöde findet oder was auch immer - (konstruktive) Krtik ist immer gut ;)

Ich hoffe ich kann in den nächsten Tagen die ersten Themen/ToDo´s angehen :)

Silverlight 2 Beta 2 kommt diese Woche

03.06.2008 13:47:00 | Steffen Ritter

Heute in der Keynote der Microsoft TechEd kündigten Bill Gates und Soma Somasegar an, dass Silverlight 2 Beta 2 im Verlauf dieser Woche veröffentlicht wird und als kostenloser Download zur Verfügung stehen wird.

Neue Highlights in Silverlight 2 Beta 2:

  • Optimiertes UI Framework: Beta 2 bringt deutliche Verbesserungen im Bereich Animationen, Fehlerverarbeitung und –reporting, Automatisierung, Barrierefreiheit, Tastaturunterstützung und bei der Performance. Die Kompatibilität zwischen Silverlight und WPF wurde zudem erweitert.
  • Erweiterte Controls: Silverlight 2 Beta 2 enthält ein neues „Templating Modell“ namens Visual State Manager, mit dem sich komfortabel Templates (Vorlagen) für Controls anlegen lassen. Außerdem neu sind TabControl, Text Wrapping, Scrollbars für Textboxen und neue DataGrid-Funktionen wie Autosize, Reorder, Sort, sowie ebenfalls deutliche Performancegewinne bei Controls. Die meisten Controls sind nun in die Runtime integriert und müssen nicht mehr mit der Anwendung  ausgeliefert werden.
  • Verbesserte Netzwerkunterstützung: Zu den neuen Netzwerkfunktionen in Beta 2 gehören domainübergreifender Zugriff und erweiterte Sicherheitsfunktionen, Uploadunterstützung für Webclients und Duplexkommunikation („Push“ vom Server zum Silverlight-Client).
  • Rich Base Class Library: Beta 2 verfügt über verbesserte Threadingfähigkeiten, LINQ-to-JSON, ADO.NET Data Services, bessere Unterstützung für SOAP, sowie viele weitere Verbesserungen die Networking und Datenverarbeitung deutlich komfortabler und einfacher machen.
  • Verbessertes Deep Zoom: Beta 2 führt ein neues XML-basiertes Dateiformat für Deep Zoom Image Tiles (Bildkacheln) ein, sowie das neue MultiScaleTileSource -- somit können bestehende Bild-/Kacheldatenbanken mit Deep Zoom verwendet werden.

Silverlight 2 Beta 2 wird im Laufe der Woche veröffentlicht und kann dann hier herunter geladen werden: http://www.silverlight.net
Außerdem ab sofort verfügbar ist Expression Blend 2.5 June Preview: http://www.microsoft.com/expression/try-it

Silverlight 2 ermöglicht schnelle Vektorgrafiken, flüssige Animationen, Audiointegration, kostengünstige Videopräsentation und Videostreaming bis hin zu HD-Video im Web. Damit lassen sich moderne Benutzeroberflächen und faszinierende Multimediaerlebnisse schaffen, die es Anwendern ermöglichen Videos in HD-Qualität gemeinsam mit Freunden online zu erleben. Ebenso ist es die optimale Plattform, um Businessanwendungen entwickeln, die Geschäftsinformationen dynamisch und plattformunabhängig im Webbrowser visualisieren (eine faszinierende Silverlight-Beispielanwendung die viele der Funktionen von Silverlight demonstriert ist hier verfügbar: http://www.mscui.net/PatientJourneyDemonstrator).

Wöchentliche Rundablage: ASP.NET MVC, .NET, ADO.NET Data Services, Silverlight, WPF…

02.06.2008 22:31:14 | Robert Mühsig

ASP.NET MVC / ASP.NET:

.NET:

ADO.NET Data Services:

WPF / Windows Presentation Foundation / Silverlight:

Visual Studio:

Web:

Live Mesh:

Mix:

Gratis WinForm Controls - Free WinForm Controls

02.06.2008 21:32:00 | Ozgur Aytekin

ComponentFactory bietet diverse Gratis WinForms Controls unter dem Namen Krypton Toolkit an. Diese Controls können gemäß Informationen auf der ComponentFactory Homepage auch Kommerziell eingesetzt werden.

Die URL für die Installationsdatei wird an die angegebene E-Mail Adresse geschickt. Während der Installation werden verschiedene Beispiel-Projekte und eine Beispiel-Applikation mit dem Namen Krypton Explorer installiert.

Mit Hilfe der Krypton Explorer können die verschiedene Controls getestet werden.

image

Innerhalb der Beispiel-Applikation kann der Entwickler auch die einzelne Eigenschaften der Controls ändern und den Verhalten des Controls testen.

image

Folgende Controls sind Bestandteil des Krypton Toolkits:

Button - KryptonButton ListBox - KryptonListBox
CheckButton - KryptonCheckButton CheckedListBox - KryptonCheckedListBox
DropButton - KryptonDropButton TextBox - KryptonTextBox
ColorButton - KryptonColorButton RichTextBox - KryptonRichTextBox
CheckSet - KryptonCheckSet ComboBox - KryptonComboBox
CheckBox - KryptonCheckBox Panel - KryptonPanel
RadioButton - KryptonRadioButton SplitContainer - KryptonSplitContainer
Label - KryptonLabel DataGridView - KryptonDataGridView
LinkLabel - KryptonLinkLabel ContextMenu - KryptonContextMenu
Form - KryptonForm BorderEdge - KryptonBorderEdge
Group - KryptonGroup HeaderGroup - KryptonHeaderGroup
Header - KryptonHeader Palette - KryptonPalette
Command - KryptonCommand

Die SourceCode-Dateien dieser Controls kann auch gekauft und bei Bedarf angepasst werden.

URL: Download Krypton Toolkit 2.8.5

Panorama-Fotospielereien mit Windows Live Fotogalerie

01.06.2008 22:52:00 | Steffen Ritter

Vergangenes Wochenende war ich auf Oliver Gassners Barcamp Bodensee. Als gebürtiger Lindauer musste ich natürlich die Chance nutzen und auch auf der Lindauer Insel vorbeischauen. Wegen des guten Wetters ließ ich mich dazu verleiten, mit dem Telefon ein paar Schnappschüsse vom Seehafen zu machen. Eigentlich hatte ich vor, diese später in Photoshop Elements zu einem Seehafen-Panorama zusammenzusetzen. Das Ergebnis meiner Bemühungen mit meinem viel geliebten und erprobten Photoshop Elements 5 war leider nicht sehr befriedigend:

Lindau-Panorama in PS5
 
Offensichtliche Brüche und Doppelungen verzerren das Bild – und was soll diese große weiße Fläche, wenn doch theoretisch ein nahtloser Anschluss möglich ist?

Ich war kurz davor, mir eine Trial-Version der neuen Elements 6er herunter zu laden oder gleich ein Update zu kaufen, als mich gerade noch rechtzeitig ein Kollege auf unser eigenes Tool hinwies: Das kostenlose Windows Live Fotogalerie mit integrierter Panorama-Funktion.
Bei exakt demselben Ausgangsmaterial montiert Windows Live Fotogalerie ein nahezu perfektes Panorama (für die Helligkeitsunterschiede nehme ich die Schuld auf mich):

Lindau-Panorama Live Fotogalerie
 
Ich muss eingestehen: Ich bin von Windows Live Fotogalerie begeistert.

Für den Foto-Workflow und Digital Asset Management ist natürlich Expression Media – der Nachfolger von iView MediaPro – mein Tool der Wahl, aber für Panoramabilder kenne ich derzeit kein besseres Tools als Windows Live Fotogalerie.

Besagter Kollege aus der Windows Live-Abteilung hat mich außerdem darauf hingewiesen, dass derzeit ein deutschlandweiter Wettbewerb läuft, bei dem die besten mit Windows Live Fotogalerie erzeugten Panoramafotos  prämiert werden. Alle Infos und Teilnahmebedingungen hier: http://pressecenter.msn.de/377_WL_Photo_Europe.OSG

PS: Und um diesen Beitrag nicht vollkommen Silverlight-frei enden zu lassen: Ich werde jetzt umgehend versuchen, aus diesem oder einem anderen etwas größeren Panorama eine schnieke Deep Zoom-Anwendung zu machen :)

Links aus diesem Beitrag:

Regeln | Impressum