November 2006 Entries

Vortrag zur Windows Presentation Foundation am 05.12.2006

Am Dienstag den 05.12.2006 veranstaltet die [url=http://www.dnug-koeln.de].net user group Köln[/url] wieder Ihr [url=http://www.dnug-koeln.de/treffen/user-group-treffen/user-group-treffen-details/article/november-treffen-der-dnug-koeln/]monatliches Usertreffen[/url] mit einem Vortrag. Diesmal hält Stefan Lange einen Vortrag über die Windows Presentation Foundation (WPF, Codename Avalon) der Zukunft in der Windows GUI Entwicklung.
Mit der Windows Presentation Foundation (WPF) hat Microsoft nach über 20 Jahren Windows das erste Mal tatsächlich etwas grundlegend Neues zum Erstellen von Windows und Web Anwendungen geschaffen. Das Ergebnis ist wirklich beeindruckend geworden und wird die Windows Entwicklung nachhaltig verändern. In meinem Vortrag möchte ich den Versuch unternehmen Software Entwicklern die wichtigsten Konzepte anhand von Beispielen vorzustellen und auch einen kurzen Einblick in die kommenden Entwicklungswerkzeuge zu geben. Ziel des Vortrags ist es, einen Eindruck zu vermitteln, welche phantastischen Möglichkeiten sich durch den Einsatz der Windows Presentation Foundation eröffnen.
Die Teilnahme an dem Treffern in der Kölner Südstadt steht jedermann frei und ist kostenlos. Um Anmeldung wird jedoch gebeten. Wertvolle Give Aways von O'Reilly stehen auch zur Verfügung. [url=http://www.dnug-koeln.de/treffen/]Anmeldung über DNUG Homepage[/url] bzw. [url=https://www.xing.com/app/events?op=detail&id=87027]Anmeldung über XING[/url] Als Mitorganisator werde ich natürlich auch vor Ort sein.

optgroup bei DropDownList via ControlAdapter

Leider hat es MS ja mit ASP.NET 2.0 nicht geschafft auch <optgroup /> zu unterstützen. Damit könnte man die Einträge innerhalb DropDownList gruppieren. Dies würde dann so aussehen.

Dies kann man jedoch leicht nachrüsten, entweder mit einem eigenen Control oder mit einem Adapter. Wie? Das soll hier gezeigt werden. ControlAdapter sind eine ganz nette Erfindung von Microsoft, damit kann man das verhalten der eingebauten Controls überschreiben. Den Weg über den ControlAdapter hat jemand bei The Code Project [1] vollzogen.

Leider hat die veröffentliche Variante einige Probleme beim Postback, da keinerlei Viewstate verwaltet wird, und somit die Validierung nicht möglich ist und die Gruppierung verloren geht. Ich habe das ganze ein wenig aufgeräumt, die Viewstate Unterstützung eingebaut und es ist was brauchbares dabei rumgekommmen, warum soll es also weiter nur auf meiner Festplatte versauern. Die Anwendung ist denkbar einfach, ein vorhandenes ListItem muss einfach um das Attribute "optionGroup" mit dem Namen der Gruppe erweitert werden. Dies entweder direkt im Markup-Code

<asp:DropDownList id="foo" runat="server" />
<asp:ListItem optionGroup="Gruppe1">Eintrag 1</asp:ListItem>
<asp:ListItem optionGroup="Gruppe1">Eintrag 2</asp:ListItem>
<asp:ListItem optionGroup="Gruppe2">Eintrag 3</asp:ListItem>
<asp:ListItem optionGroup="Gruppe2">Eintrag 4</asp:ListItem>
<asp:ListItem optionGroup="Gruppe3">Eintrag 5</asp:ListItem>
<asp:ListItem optionGroup="Gruppe3">Eintrag 6</asp:ListItem>
<asp:ListItem optionGroup="Gruppe3">Eintrag 7</asp:ListItem>
<asp:ListItem optionGroup="Gruppe4">Eintrag 8</asp:ListItem>
</asp:DropDownList>

oder via Programm-Code

ListItem item = new ListItem();
item.Attributes["optionGroup"] = "Gruppe1"; 

In diesem ControlAdapter wird RenderContents() um die ListItems entsprechend zu rendern und in eine <optgroup /> zu packen. Für den ViewState wird dann noch LoadAdapterViewState() und SaveAdapterViewState() überschrieben. Wichtig ist dann noch nach dem wiederherstellen nach dem laden des ViewsStates die optionGroup Einstellungen wieder herzustellen, die passiert passenderweise in OnPreRender(). Damit die Event Validierung nicht fehlschlägt muss jeder Wert der ListItems noch mit Page.ClientScript.RegisterForEventValidation() registriert werden. Der ControlAdapter dann einfach nur noch in die Webanwendung einbauen. Dazu die beigefügte DropDownList.browser Datei in den Ordner App_Browsers ablegen, ist dieser nicht da, dann einfach anlegen. Viel Spaß.


[1] http://www.codeproject.com/aspnet/DropDownListOptionGroup.asp

[2] http://der-albert.com/uploads/DropDownListAdapter.zip

Anti-Cross Site Scripting Library nun in der Version 1.5 verfügbar

Microsoft hat die Anti-Cross Site Scripting Library [1] nun in der Version 1.5 [2] veröffentlicht. Damit können Webanwendungen gegen Cross Site Scripting (XSS) abgehärtet werden. Mit der aktuellen Version sind auch Methoden für das absichern von JavaScript, Attribute, Xml und mehr hinzugekommen. Desweiteren braucht man nun kein Full-Trust Level für die Anwendung mehr und darf nun auch offiziell in produktiven Systemen eingesetzt werden. Genaures findet sich im Blog des ACE-Team [3]. Wer also Fremddaten in seine Webanwendung lässt , sei es nun über ein Formular oder bei einem Import aus anderen Datenquellen, sollte auf jedenfall einen Blick darauf werfen.
[1] [url=http://der-albert.com/archives/27-Anti-Cross-Site-Scripting-Library-1.0-von-Microsoft-fuer-ASP.NET.html]Anti-Cross Site Scripting Library 1.0 von Microsoft für ASP.NET[/url] [2] [url=http://www.microsoft.com/downloads/details.aspx?FamilyId=EFB9C819-53FF-4F82-BFAF-E11625130C25&displaylang=en]Download ACSS Library 1.5[/url] [3] [url=http://blogs.msdn.com/ace_team/archive/2006/11/20/microsoft-anti-cross-site-scripting-library-v1-5-is-done.aspx]Blog Eintrag vom ACE Team[/url] [4] [url=http://de.wikipedia.org/wiki/XSS]Wikipedia Artikel: Cross Site Scripting[/url]

Interfaces müssen nicht implementiert werden!

Aus der Abteilung, Sprachfeatures die vielleicht nicht jeder kennt. Derzeit ja schwer in Mode in den Blogs :), ein Trend auf den ich mal aufspringe.     Diesmal das Interface:   

"Landläufige" Meinung: Erstellt man ein Interface, dann muss eine darauf aufbauende Klasse alle Methode und Eigenschaften implemtieren. Falsch[:  Muss sie nicht!   Jedenfalls nicht wenn sie schon implemtiert sind, z. B. in einer Basisklasse. Und dies ist auch ganz praktisch. Dies zeige ich hier an einem einem Beispiel.    

Wenn ich z.B. mit dem Modell View Presenter Pattern [1] eine Anwendungsoberfläche erstelle und dort den Presenter einen passiven View steuern lasse. Dann braucht der Presenter, je nach Variation des MVP-Patterns, Zugriff auf die Controls, jedoch ist es nicht unbedingt ratsam die eigentliche Control-Objekte nach "draußen" zu führen.

Da man sich damit die Möglichkeit verbaut den Presenter auch für verschiedenen View-Variationen einzusetzen.     Denn stellt man es halbwegs geschickt an, so kann der gleiche Presenter für WebForms, WinForms oder GTK# (bei Mono) eingesetzt werden. So das ein großteil der GUI Logik (füllen mit Daten, Zustände der Controls, Überprüfung der Eingaben) nur einmal geschrieben werden muss.    Leider hat Microsoft beim .NET Framework es ja verpasst die ASP.NET Controls und WinForms Controls auf eine halbwegs gemeinsame Basis zu stellen. Doch mit Interfaces kann dem ein wenig nachgeholfen werden. Ohne das man, bei den meisten Controls, viel Programmieren muss.    So habe ich z.B. in einer Anwendung mir folgende Interfaces definiert um z.B. einen Button zu steuern.   

public interface IControl
{
    bool Enabled { get; set; }
    bool Visible { get; set; }
    void Focus();
}

public interface IButton : IControl
{
    string Text { get; set; }
    event EventHandler Click;
}

Damit ich mit dem Interface auch arbeiten kann muss ich mir selbstverständlich einen eigenen Button erstellen, der darauf basiert. Denn ein Arbeiten mit &lt;asp:Button /&gt; fällt flach, da er weder dieses Interface noch ein anderes ähnliches Interface kennt.    Doch mit diesem gewählten Interface fällt die Implementierung eines eigenen Buttons trotzdem sehr klar und einfach aus.   

namespace DerAlbert.BaseControls.Web
{
    public class Button : System.Web.UI.WebControls.Button, IButton
    {
    }
}

Das war es schon, fertig ist mein eigener Button und kann einer Webanwendung einsetzen. Das Interface muss nun nicht mehr implementiert werden, da die Basisklasse schon eine Implementation der Methoden und Eigenschaften zu Verfügung stellt, unabhängig von meinem Interface.    Dies ist ein wesentlicher Unterschied zwischen Interfaces und rein abstrakten Klassen wie sie z.B. in C++ eingesetzt werden um ähnliches zu erreichen.    In der Anwendung sieht dies dann so aus, ich definiere eine Inteface für ein View 

namespace DerAlbert.Community.Views
{
    public interface IImagesEditView : IView
    {
        IButton ButtonSave { get; }
    }
}

    Nun baue ich mir eine entsprechende Form auf Basis dieses Interface und entwickle den Presenter gegen dieses Interface. Fertig ist mein universeller Presenter.    Mit einem WinForm Button ist dies auch möglich, so kann ich den Presenter gegen das Interface des Controls entwickeln und muss mich nicht drum kümmern ob WinForm, WebForm und hoffentlich auch WPF.    Natürlich ist nicht jedes Control so einfach umzusetzen. Für ein DropDownList oder TreeView muss man schon ein paar Hindernisse aus dem Weg räumen um sowohl das Control sowie ListItem und TreeNode mit einem Interface zu versehen.    Auch gleichen sich WebForm und WinForm nicht in allen Controls so einfach wie beim Button, dann muss man halt dass was jeweils anders ist nachprogrammieren. Doch dies ist ein einmaliger Aufwand.   

  [1] http://martinfowler.com/eaaDev/

HTMLElement-Scrollposition bei einem PostBack merken

Heute in den Newsgroups [2], es fragte Fabian Nowothnik danach wie man nach einem Postback die Scrollpositionen von einzelnen Elementen wieder herstellen kann.

Für die Scrollposition der Seite gibt es dies ja seit ASP.NET 2.0 schon in der Serienausstattung. Ein Scrolling innerhalb eines DIV-Elements "passiert" ja schnell, in dem innerhalb eine DIV-Elements mehr reinpackt als die festgesetzen Ausmaße zulassen. Nun einfach den CSS-Style "overflow:auto;" oder "overlow:scroll;" gesetzt dann kann man darin scrollen, jedoch verliert sich die Scroll-Position nach dem Postback. Das ist schade.

Prinzipiell sollte es doch ganz einfach sein etwas dagegen zu machen. Position der Elemente vor dem Postback merken und dann direkt einfach beim neuladen wieder herstellen. Wäre doch auch nett wenn man dies dann einfach via kleinem Control machen kann einfach auf jeder Seite einsetzen kann. Gesagt getan, hier ist das Ergebnis das "RememberElementScrollPosition" WebControl für ASP.NET 2.0 :).

Gleich vorweg, dazu ist JavaScript notwendig, ohne geht's nicht. Die Anwendung selbst ist denkbar einfach. Nachdem man den Quelltext [1] in eine Assembly oder App_Code Ordner gepackt hat muss man das Control dem Web bekannt machen, dazu in der Web.config zu der Controls-Auflistung hinzufügen, hier ein Beispiel, es muss den eigenen Gegebenheiten angepasst werden.

<system.web> 
  <pages> 
    <controls> 
      <add namespace="DerAlbert.Community.Web.UI.WebControls" 
           assembly="DerAlbert.Community.Web" 
           tagprefix="dac" /> 
    </controls> 
  </pages> 
</system.web>

Auf der Seite dann folgendes

<dac:rememberelementscrollposition id="RESP1" runat="server"> 
    <dac:controlid>MyPanel</dac:controlid> 
    <dac:controlid>Div1</dac:controlid> 
</dac:rememberelementscrollposition>

Für jedes Element/Control ein <dac:ControlID/> hinzufügen. Es wird erst das passenden Server-Control gesucht und verwendet, wird keins gefunden so wird die angegebene ID direkt als ClientID verwendet. So das man

<asp:panel id="MyPanel" runat="server">Der rieisige Inhalt</asp:panel> 

oder auch

<div id="Div1">Größer, schneller weiter</div>

verwenden kann. Je nach Vorliebe, aber eine ID muss vorhanden sein. Mehr muss man nicht machen.

Damit ich vor dem Postback die Daten auslesen kann Registriere ich mir mit ClientScript.RegisterOnSubmitStatement() eine Javascript Anweisung welche das sammeln der Scrollpositionen aufruft und in einem HiddenField ablegt, damit diese den Postback ohne weitere Tricks überleben. Beim wiederladen der Seite sollen die gespeicherten Positionen wieder gesetzt werden, dafür muss wieder eine JavaScript Anweisung registriert werden diesmal mit ClientScript.RegisterStartupScript(). Damit wird die übergebenen Anweisung ans Ende der Seite gelegt und automatisch aufgerufen nachdem der eigentliche Seiteninhalt schon vorhanden ist.

Das JavaScript selbst ist objektorientiert aufgebaut, sollte jedoch nicht so schwer sein zu verstehen. Es merkt sich die scrollTop und scrollLeft Werte des HTML-Elements und packt diese zusammen mit der ID des Elements in das Hiddenfield. Selbstverständlich für alle angebenen Elemente, jedoch nur wenn man die Element auch wirklich in einem "gescrolltem" Zustand sind. Nun genug geredet. Nutzt das Teil wenn Ihr wollt.

[1] Download Quelltext

[2] Newsgroup-Anfrage

.NET Framework 3.0 ist Final!

Heute hat Microsoft die finale Version von .NET 3.0 veröffentlicht. Das .NET Framework 3.0 ist die Version 2.0 des Frameworks zuzüglich der Module Windows Presentation Foundation (WPF), Windows Comunication Foundation (WCF), Windows Workflow Foundation (WF) und Windows Card Spaces (WPF). Alle Anwendungen die für 2.0 entwickelt worden sind laufen auch unter 3.0. Jedoch ist zu beachten das ab der Version 3.0 Windows 2000 und 98/Me nicht mehr unterstützt wird. Also Rechnervorraussetzungen empfiehlt Microsoft 1Ghz Prozessortakt und 256 MB Arbeitsspeicher. Sollte man eine Vorabversion von .NET 3.0 auf dem Rechner gehabt haben, so ist unbedingt alles davon vorher zu entfernen. Genaueres dazu findet sich in den Release Notes des Frameworks. [url]http://msdn.microsoft.com/windowsvista/support/relnotes/netfx3/default.aspx[/url] Hier die dazugehörigen Downloads. [url=http://go.microsoft.com/fwlink/?LinkID=70848]Download .NET FX 3.0 x86[/url] [url=http://go.microsoft.com/fwlink/?LinkID=70849]Download .NET FX 3.0 x64[/url] [url=http://www.microsoft.com/downloads/details.aspx?FamilyId=C2B1E300-F358-4523-B479-F53D234CDCCF&displaylang=en].NET 3.0 und Windows Vista SDK[/url] [url=http://www.microsoft.com/downloads/details.aspx?FamilyId=F54F5537-CC86-4BF5-AE44-F5A1E805680D&displaylang=en]Visual Studio 2005 Extension for .NET 3.0 (WPF, WCF) November CTP[/url] [url=http://www.microsoft.com/downloads/details.aspx?FamilyId=5D61409E-1FA3-48CF-8023-E8F38E709BA6&displaylang=en]Visual Studio 2005 Extension for Workflow Foundation[/url]

Download der MSDN Library August 2006

Microsoft hat die englische MSDN Library von August 2006 in der CD-Version zum kostenlosen Download zu Verfügung gestellt. Wer sie also noch nicht hat, kann sie nun runterladen und die Entwickler Dokumentation aktualisieren. [url=http://www.microsoft.com/downloads/details.aspx?FamilyId=6671F8DA-B0CB-41DE-AD93-798FC71D344D&displaylang=en]MSDN Library August 2006 Download[/url]

Generische ConfigurationElementCollection

Baut man sich für seine .NET 2.0 Anwendung eigene Konfigurationsbereiche, dann braucht man früher oder später auch eine Liste von Elementen. Braucht man dies öfters, dann wird es ein wenig nervig, da man ein wenig Grundtipparbeit für jede Auflistung hat. Hier ein generischer Ansatz für ConfigurationElementCollection.

public abstract class ConfigurationElementCollection<T> : ConfigurationElementCollection
        where T : ConfigurationElement
{
    protected override ConfigurationElement CreateNewElement()
    {
        return (ConfigurationElement)Activator.CreateInstance(typeof(T));
    }

    public T this[int index]
    {
        get { return (T)base.BaseGet(index); }
        set
        {
            if (base.BaseGet(index) != null)
            {
                base.BaseRemoveAt(index);
            }
            this.BaseAdd(index, value);
        }
    }

    public new T this[string key]
    {
        get { return (T)base.BaseGet(key); }
    }
}

Nun braucht man nur noch ein ConfigurationElement. Hier ein Beispiel aus einer aktuellen Anwendung von mir.

public class EntryObjectSettings : ConfigurationElement
{
    [ConfigurationProperty("name", IsRequired = true)]
    public string Name
    {
        get { return (string) base["name"]; }
        set { base["name"] = value; }
    }

    [ConfigurationProperty("type", IsRequired = true)]
    [TypeConverter(typeof (TypeTypeConverter))]
    [SubclassTypeValidator(typeof (BaseEntry))]
    public Type Type
    {
        get { return (Type) base["type"]; }
        set { base["type"] = value; }
    }

    [ConfigurationProperty("template", IsRequired = false)]
    [FileExistsValidator]
    public string Template
    {
        get { return (string) base["template"]; }
        set { base["template"] = value; }
    }
}

Jetzt erstellt man eine ConfigurationElementCollection für sein ConfigurationElement zu. Das einzige was nun noch gemacht werden muss, ist das überschreiben von GetElementKey() damit die Collection auch einen eindeutigen Key für jeden Eintrag erhält.

[ConfigurationCollection(typeof (EntryObjectSettings))]
public class EntryObjectSettingsCollection : ConfigurationElementCollection
{
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((EntryObjectSettings) element).Name;
    }
}
    

Mit einem eigenem Validator die Angaben in der Konfigurations-Datei überprüfen

Unter .NET 2.0 ist es mit Ableitungen von [b]ConfigurationSection[/b] und [b]ConfigurationElement[/b] sehr einfach möglich die Konfigurations-Dateien um eigene Abschnitte zu erweiteren die dann Typsicher eingelesen und verwendet werden können. Möchte man nun die Attribute der Konfiguration direkt beim einlesen überprüfen und nicht erst wenn der Wert zur Laufzeit verwendet wird, so geht dies natürlich auch. So werden ein paar Standard-Validatoren mitgeliefert z.B. [b]IntegerValidator[/b] und [b]SubclassValidator[/b]. Einen eigenen Validator zu schreiben ist, wie so oft in .NET, natürlich auch möglich. Dazu sind nur zwei Schritte notwendig. [list] [*]Schreiben eines Validators auf Basis von [b]ConfigurationValidatorBase[/b][*]Schreiben eines Attributes auf Basis von [b]ConfigurationValidatorAttribute[/b][/list]Bei [b]ConfigurationValidatorBase[/b] müssen die Methoden [b]CanValidate()[/b] und [b]Validate()[/b] überschrieben werden. Hier an einem Beispiel eines Validators der überprüft ob eine in einer Eigenschaft angegebene Datei auch wirklich vorhanden ist. [c#] using System; using System.Configuration; using System.IO; namespace DerAlbert.Community.Configuration { public class FileExistsValidator : ConfigurationValidatorBase { public override void Validate(object value) { if (string.IsNullOrEmpty((string)value)) return; string path = GetPath((string) value); if (!File.Exists(path)) throw new ConfigurationErrorsException( string.Format("The file '{0}' does not exists", path)); } private string GetPath(string valuePath) { if (valuePath.StartsWith("~/") || valuePath.StartsWith("~\\\")) { return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, valuePath.Substring(2)); } else return valuePath; } public override bool CanValidate(Type type) { return (type == typeof(string)); } } } [/c#] Nun hat man zwar einen schönen Validator, jedoch muss dieser noch an die entsprechende Eigenschaft gebunden werden. Dies geschieht über ein Attribute auf Basis von [b]ConfigurationValidatorAttribute[/b]. Hier muss im einfachsten Fall nur die Eigenschaft [b]ValidatorInstance[/b] überschrieben werden um eine neue Instanz des eigenen Validators zu übergeben. [c#] using System.Configuration; namespace DerAlbert.Community.Configuration { public class FileExistsValidatorAttribute : ConfigurationValidatorAttribute { public override ConfigurationValidatorBase ValidatorInstance { get { return new FileExistsValidator(); } } } } [/c#] Sind zusätzliche Parameter für den Validator erforderlich um eine Gültigkeit zu ermitteln, so sind diese über den Konstrukturen des Attributes anzunehmen. Diese können dann direkt bei der Angabe des Attributes bei der Eigenschaft erfasst werden. Diese Parameter müssen wiederum an die Validator weitergeleitet werden z.B. mit dessen Konstruktor. In der Anwendung bei einem [b]ConfigurationElement[/b] sieht dies dann so aus. Gebunden ist der Validator hier an die Template-Eigenschaft. Somit wird schon beim einlesen der Konfiguration geprüft ob die Datei vorhanden ist und nicht erst wenn diese in der Anwendung wirklich gebraucht wird. [c#] using System; using System.ComponentModel; using System.Configuration; using DerAlbert.Community.Objects; namespace DerAlbert.Community.Configuration { public class EntryObjectSettings : ConfigurationElement { [ConfigurationProperty("name", IsRequired = true)] public string Name { get { return (string)base["name"]; } set { base["name"] = value; } } [ConfigurationProperty("type", IsRequired = true)] [TypeConverter(typeof(TypeTypeConverter))] [SubclassTypeValidator(typeof(BaseEntry))] public Type Type { get { return (Type)base["type"]; } set { base["type"] = value; } } [ConfigurationProperty("template", IsRequired = false)] [FileExistsValidator] public string Template { get { return (string)base["template"]; } set { base["template"] = value; } } } } [/c#] Der Vollständigkeithalber hier der entsprechende Abschnitt in der Konfigurationsdatei [xml] [/xml]