Archive for the 'DotNet' Category

#ASP.NET: Back!

Ja, manchmal geschehen noch Zeichen und Wunder. Eines dieser Zeichen ist die Rückkehr von ASP.extra, meiner vor mehreren Jahren eingestellten Seite rund um ASP, ASP.NET und verwandte Web-Technologien. Eingestellt worden ist sie damals nicht etwa wegen mangelnder Resonanz, sondern ganz im Gegenteil: Wegen zu hoher Resonanz.

Es wurde damals notwendig, die alte Webseite grundlegend zu überarbeiten und neu zu gestalten – was jedoch aus diversen privaten und zeitlichen Gründen unterblieb. Da dieses Blog sich jedoch in den letzten Monaten mehr in Richtung Politik orientiert hat, bin ich in mich gegangen und habe beschlossen, ASP.extra wieder auferstehen zu lassen. Damit gibt es eine klare Trennung: karsan.de für persönliche und politische Dinge, ASP.extra für technologische Sachen.

Optisch hat sich für die alten ASP.extra-Benutzer nicht soooo viel getan (gut, die Navi und der ganze Kram sind jetzt auf der anderen Seite), aber technisch ist die Plattform WordPress statt eines proprietären Systems. Die Vorteile liegen auf der Hand: Die Webseite ist standardkonform, der Weiterentwicklung sind somit keine Grenzen gesetzt und wenn ich ein nettes Plugin finde, baue ich es auch ein.

Momentan fehlt es noch an einem kleinen Detail auf www.aspextra.de – den Inhalten. Die kommen aber im Laufe der Zeit, versprochen. Bookmarks können gesetzt werden. ;-)

DOTNET: Meine Top #3 Entwicklertools

Klassisches Me-Too-Posting, aber vielleicht doch interessant. ;-) Nach Peter, Stefan und Jürgen (und einigen anderen, wie man hier nachlesen kann), hier nun auch meine Top #3 Entwicklertools.

  • An erster Stelle kommt bei mir ganz klar ReSharper in Version 4.1 – Refactoring-Features und Ergänzungen / Erweiterungen zur IDE, die sowohl für C# als auch für VB funktionieren. Ist jeden einzelnen Cent wert.
  • An zweiter Stelle kommt in jedem Fall VisualSVN als Quellcodeverwaltungstool. Damit kann ich gegen beliebige Subversion-Repositories arbeiten und halte meine Daten versioniert und synchron über Maschinen hinweg.
  • An dritter Stelle kommt bei mir kein echtes Tool, sondern ein Framework: NHibernate. Für mich DER .NET-O/R-Mapper schlechthin. Anders als das Entity-Framework kann man es ohne Umstände auch in existierende Objekthierarchien einbinden und auch an finsterste Datenbankschemata anpassen. Schnell, flexibel und was Mappings und Handhabung anbelangt kompatibel mit dem Java-Hibernate, so dass die Dokumentation hier einfach passt und man sogar mit kompletten Hibernate-Büchern arbeiten kann.

Gäbe es die Möglichkeit, mehr als drei Tools anzugeben, fielen mir spontan noch weitere Kandidaten ein: DotTrace als Profiler, iTextSharp für die PDF-Verarbeitung, NUnit als unabhängiges Komponententestframework oder auch die DevExpress-Windows-Forms-Steuerelementebibliotheken.

DOTNET: Weihnachten, schon jetzt

Die Firma DevExpress, vielen sicherlich ein  Begriff aufgrund der Refactor!-PlugIns fürs Visual Studio (VB und ASP.NET), macht allen .NET-Entwicklern ein hübsches, kleines Weihnachtsgeschenk: 60 Controls (WindowsForms und WebForms) für umme.

Was man tun muss? Nix anderes, als sich unter http://www.devexpress.com/60 zu registrieren und anschließend im Download-Center die Komponenten herunter zu laden. Bei der Installation kann man natürlich die Trial-Versionen angewählt lassen, muss es aber nicht. ;-)

Also, frohes Fest!

WCF: Formularbasierte Authentifizierung

Recht cool ist das WCF-Feature der formularbasierten Authentifizierung, die sich der ASP.NET-Membership- und –Rollenprovider bedient. Uncool ist jedoch, dass dieses Feature nicht mit dem Standard-ConnectionString funktioniert.

Ich habe geschlagene acht Stunden benötigt, bis ich herausgefunden habe, dass das Scheitern meiner Bemühungen nicht ausschließlich auf mein eigenes Unvermögen, sondern unter anderem auch auf den ConnectionString zurück zu führen ist. Ich bin mir nicht ganz sicher, woran es tatsächlich liegt, ich denke aber, dass im Kontext eines WCF-Dienstes die Auflösung des im Standard-ConnectionString definierten Platzhalters für das Datenverzeichnis nicht funktioniert.

Die Lösung besteht darin, die Datenbank fest in eine SQL Server-Instanz einzuhängen und dann den Standard-ConnectionString zu überschreiben:


  
         connectionString=”SERVER=…; INITIAL CATALOG=…; …” />

Anschließend funktioniert es auch wie gewünscht.

SICHERHEIT: Schutz vor SQL-Injection

Das klassische Argument für den Einsatz parametrisierter Statements ist der Schutz vor SQL-Injection. SQL-Injection bezeichnet dabei den Vorgang des Einschleusens von fremden SQL-Statements in eigene SQL-Statements.

Beispiel: Die klassische Benutzer-Authentifizierung, bei der Benutzername und Kennwort übergeben werden. Der typische (C#-) Code für das generieren des SQL-Statements sieht dann in etwa so aus:

   // Werte einlesen
   String username = tbLoginName.Text;
   String password = tbPassword.Text;

   // Statement bauen
   String sqlStatement = “SELECT COUNT(1) FROM Users ” +
      “WHERE Username = ‘” + username + “‘ ” +
      “AND Password = ‘” + password + “‘”;

   // Command erzeugen
   DbCommand command = conn.CreateCommand();
   command.CommandText = sqlStatement;

   // Statement ausführen
   int result = (int) command.ExecuteScalar();

Lassen Sie uns mal einige Varianten für Benutzernamen und Kennwörter durchspielen.

Variante #1:

  • Benutzername: foo
  • Kennwort bla
  • SQL-Statement: SELECT COUNT(1) FROM Users
    WHERE Username = ‘foo‘ AND Password = ‘bla

Kein Problem hier, alles gut. Wenn Benutzername und Kennwort passen, dann ist es fein.

Variante #2:

  • Benutzername: ‘ OR 1=1 –
  • Kennwort: Weiß nicht
  • SQL-Statement: SELECT COUNT(1) FROM Users
    WHERE Username = ‘‘ OR 1=1 –‘ AND Password = ‘Weiß nicht

Großes Problem hier. Das SQL-Statement sieht irgendwie krank aus und kann ganz sicher nicht funktionieren. Sicher?! Doch, es funktioniert, denn die beiden aufeinanderfolgenden Bindestriche interpretiert ein SQL-Server beispielsweise als Kommentar – alles, was danach kommt, wird also ignoriert. Das tatsächlich ausgeführte SQL-Statement sieht also so aus:

  • SQL-Statement: SELECT COUNT(1) FROM Users WHERE Username = ” OR 1=1

Ganz gewaltiges Problem jetzt: Die Abfrage selektiert alle Datensätze, deren Username-Spalte entweder leer ist, oder für die 1=1 gilt. Und da 1=1 immer gilt, werden also alle Datensätze selektiert. Die Anmeldung wird jetzt also klappen.

Variante #3:

  • Benutzername: ‘; INSERT INTO Users (‘karsten’, ‘sicher’) –
  • Kennwort: Keine Ahnung
  • SQL-Statement: SELECT COUNT(1) FROM Users
    WHERE Username = ‘‘; INSERT INTO Users (‘karsten’, ‘sicher’) –‘ AND Password = ‘Keine Ahnung

Ebenfalls großes Problem: Nun werden zwei SQL-Statements ausgeführt, denn das Semikolon trennt SQL-Statements voneinander. Alles, was nach dem Kommentar-Zeichen kommt, wird komplett ignoriert. Das eigentliche SQL-Statement sieht also so aus:

  • SQL-Statement: SELECT COUNT(1) FROM Users WHERE Username = ”; INSERT INTO Users (‘karsten’, ‘sicher’)

Super, jetzt haben wir einen zweiten Datensatz in der Tabelle!

Die Lösung

Der Lösungsansatz ist ganz einfach: Parametrisierte SQL-Statements verwenden. Dazu muss der obenstehende Code nur ein wenig abgewandelt werden:

   // Werte einlesen
   String username = tbLoginName.Text;
   String password = tbPassword.Text;

   // Statement bauen
   String sqlStatement = “SELECT COUNT(1) FROM Users ” +
      “WHERE Username = @Username ” +
      “AND Password = @Password”;

   // Command erzeugen
   DbCommand command = conn.CreateCommand();
   command.CommandText = sqlStatement;

   // Parameter anfügen
   DbParameter param = command.CreateParameter();
   param.ParameterName = “@Username”;
   param.Value = username;
   command.Parameters.Add(param);

   param = command.CreateParameter();
   param.ParameterName = “@Password”;
   param.Value = password;
  command.Parameters.Add(param);


   // Statement ausführen
   int result = (int) command.ExecuteScalar();

Nun werden die Daten anders an die Datenbank übergeben – nämlich in zwei Schritten: Zuerst wird das Statement übergeben, anschließend erfolgt die Übergabe der Parameter. Auf Ebene der Datenbank wird nun nicht etwa ein SQL-Statement dynamisch zusammengebaut, sondern es wird wie in Form eines Prozeduraufrufs verarbeitet. Für die Beispiele gilt also:

Variante #1:

  • Benutzername: foo
  • Kennwort bla
  • SQL-Statement: SELECT COUNT(1) FROM Users
    WHERE Username = @Username AND Password = @Password

Kein Problem hier, alles gut. Wenn Benutzername und Kennwort passen, dann ist es fein.

Variante #2:

  • Benutzername: ‘ OR 1=1 –
  • Kennwort: Weiß nicht
  • SQL-Statement: SELECT COUNT(1) FROM Users
    WHERE Username = @Username AND Password = @Password

Kein Problem hier. Wenn es nicht zufällig einen Datensatz mit dem Benutzernamen ‘ OR 1=1 – und dem Kennwort Weiß nicht gibt, dann wird nix gefunden. Und wenn es den Datensatz gibt, ist der Benutzer ordnungsgemäß authentifiziert.

Variante #3:

  • Benutzername: ‘; INSERT INTO Users (‘karsten’, ‘sicher’) –
  • Kennwort: Keine Ahnung
  • SQL-Statement: SELECT COUNT(1) FROM Users
    WHERE Username = @Username AND Password = @Password

Kein Problem hier. Wenn es nicht zufällig einen Datensatz mit demBenutzernamen ‘; INSERT INTO Users (‘karsten’, ‘sicher’) – und dem Kennwort Keine Ahnung gibt, dann wird nix gefunden. Und wenn es den Datensatz gibt, ist der Benutzer ordnungsgemäß authentifiziert.

Also, keine Sicherheitsprobleme bei Verwendung von parametrisierten Statements.

Wer mehr über SQL-Injection wissen möchte, sollte dringend einen Blick in die Wikipedia werfen.

DOTNET: Nehmt endlich parametrisierte SQL-Statements!

Meine Güte, ich halts langsam nicht mehr aus: Viel zu viele “Entwickler” verwenden klassische (=dynamisch zusammengestellte) SQL-Statements und wundern sich dann über komischste Seiteneffekte. Dabei kann es doch so einfach sein, wenn man nur mal auf die Ratschläge hören würde, die einem an jeder Ecke entgegen gerufen werden.

Die wichtigsten Gründe für parametrisierte Statements sind:

  • Schutz vor SQL-Injection
  • Keine “komischen” Effekte beim Einfügen von Daten
  • Höhere Performance von SQL-Statements
  • Bessere Wartbarkeit von Applikationen

Für die klassischen SQL-Statements spricht heutzutage nichts mehr. Das gilt sowohl für Java, als auch für .NET-Applikationen. Um so schlimmer, dass es immer noch Bücher und Tutorials gibt, die dennoch diese völlig veraltete, unperformante und unsichere Art der Datenbankansprache postulieren.

DOTNET: Hotfix-Pack für VS 2008 und VWD erschienen

Die Kollegen in Redmond haben heute Nacht ein Hotfix-Pack für VS 2008 veröffentlicht. Dabei werden diverse Bugs und Fehler adressiert, die speziell im Visual Web Developer aufgetreten sind.

Download hier. Danke hierher.

DOTNET: VisualSVN

Ich habs gerade gekauft, nachdem ich es die letzten Tage intensiv getestet habe: Das VisualSVN-Plugin für Visual Studio. Damit geht aus dem VS heraus die Quellcode-Verwaltung gegen Subversion ohne Probleme – und anders als AnkhSvn nutzt VisualSVN die Explorer-Erweiterung TortoiseSVN.

Ich kanns nur empfehlen – zumal die Vollversion beim derzeitigen Umrechnungskurs schlappe 35,irgendwas EUR kostet.

BRAINDUMP: Repaint ohne Flackern in Windows Forms

Wer wie ich komplexe Windows Forms Applikationen schreibt, die dann auch noch – auf speziellen Kundenwunsch hin – transparente Controls o.ä. verwenden, der kennt das Problem: Bei jedem Repaint, Resize oder Paint flackert der Bildschirm, dass es unerträglich ist.

Die einfachste Abhilfe schafft

DoubleBuffered = True

bei allen Formularen und Container-Controls. Zusätzlich (weil gerne auch Controls flackern, die über diese Eigenschaft nicht verfügen), sollte folgender Code in den Konstruktor (VB):

SetStyle(ControlStyles.UserPaint Or _
   ControlStyles.DoubleBuffer Or _
   ControlStyles.AllPaintingInWmPaint, True)

In C# siehts so ähnlich aus:

SetStyle(ControlStyles.UserPaint |
   ControlStyles.DoubleBuffer |
   ControlStyles.AllPaintingInWmPaint, true);

Das geht bei allen Controls, ggf. muss man halt eine Ableitung machen. Und dann beschwert sich auch kein Kunde mehr.

BRAINDUMP: System.Drawing.Color aus HTML-Farbcode

Die einfachste Variante, aus einem HTML-Farbcode eine korrespondiere Color-Instanz zu erstellen, liegt in der Verwendung der ColorTranslator-Klasse:

Color color = ColorTranslator.FromHtml(“#efefef”);

Alles andere artet in Denk- und Tipp-Arbeit aus.

XLINQ: Laden von XML-Dokumenten

Das Laden von XML-Dokumenten per LINQ to XML fühlt sich etwas anders an, als man dies bisher gemacht hat. Hier mal ein Beispiel, bei dem die Inhalte aller title-Knoten eines Dokuments ermittelt werden sollen.

Folgendes XML-Dokument soll verarbeitet werden (in natura gehört natürlich noch mehr dazu, aber es ist ja nur ein Beispiel):

< ?xml version="1.0"?>

  
     
     
  

  

Zuerst die klassische Vorgehensweise per XmlReader:

// XmlTextReader erzeugen
XmlReader rd = XmlReader.Create(Server.MapPath(“~/data.xml”));

// Liste der Titel
List titles = new List();

// Lesen
while (rd.Read())
{
   // Muss ein Element-Knoten sein und der Name des
   // Knotens muss “title” lauten
   if (rd.NodeType == XmlNodeType.Element
       && rd.LocalName == “title”)
   {
      // Inhalt des Knotens einlesen
      titles.Add(rd.ReadString());
   }
}

// Aufräumen!
rdr.Close();

So siehts beim XmlDocument aus (ohne XPath, nur DOM):

// Laden der Daten
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath(“~/data.xml”));

// Ermitteln der Daten
List titles = new List();

// cd-Knoten
foreach (XmlElement item in doc.DocumentElement.ChildNodes)
{
   // title-Knoten finden
   foreach (XmlElement current in item.ChildNodes)
   {
      if (current.LocalName == “title”)
      {
         // Inhalt auslesen
         titles.Add(current.InnerText);
      }
   }
}

Und nun das selbe Ergebnis per LINQ to XML:

// Laden der Daten
XDocument doc =
   XDocument.Load(Server.MapPath(“~/data.xml”));

// Ermitteln der “title”-Werte
var titles =
   from title in doc.Descendants(“title”)
   select title.Value;

// Überführen in generische Liste
List titleList = titles.ToList();

Das wars, mehr ist nicht nötig. Elegant. Von der Performance her dürfte es irgendwo zwischen XmlReader und XmlDocument liegen, wobei ich es noch nicht getestet habe.

Später mehr zu LINQ to XML – ist nämlich ein spannendes Thema!

DOTNET: VS 2008 jetzt auch auf Deutsch

Das Visual Studio 2008 gibt es jetzt auch auf Deutsch. Und als Express Edition. Letzteres genau hier. Ersteres in der MSDN.

AUA: …vs. Karsten

Ist das noch frech oder schon unverschämt von diesem Herrn?

  • Sag mal Stefan, wie ist denn die Erfahrung in Eurer Gruppe, ist es möglich mit Karsten sachlich zu reden, oder soll ich das einfach ignorieren. (hier)

Eigentlich zum Lachen, wenn es nicht so lächerlich wäre.

Wer will, kann es gerne hier diskutieren.

AUA: …vs. Java

In der C# vs. PHP-Diskussion ist erfolgreich die Java-Keule rausgeholt worden:

  • Java hat bei den Möglichkeiten “nachgeholt”, es ist wohl evtl. auch eher ein Problem, dass die Java-Entwickler ihre eigene Umgebung teilweise nicht mehr komplett verstehen/ausnutzen und so zu einem Grossteil imperformante (ich spreche gerade eher übers Web) Web-Applikationen herauskommen. (hier)

Das ist einfach nur Müll und Polemik. Muss wohl daran liegen, das Freitag ist.

AUA: Web vs. Client

Oh je, in der C#-Newsgroups gehts ab: Da hat eine gestandene Entwicklerin wohl Stress mit ihrem Chef, weil der (abgesehen davon, dass er letztlich die Weisungsbefugnis hat) eine Applikation auf Basis von Webtechnologien (egal, ob jetzt wirklich C# oder PHP, ob SQL Server, Oracle oder Postgre-SQL) erstellen lassen möchte. Sie möchte das nicht und hat dann unter dem Deckmäntelchen von “C# vs. PHP” versucht, sich argumentativ zu wappnen.

Das mag für sich durchaus zulässig sein bzw. ist schnell zu entlarven. Wenn ich mir dann aber die Kommentare der gestandenen Client-Entwickler-Fraktion (egal, ob C# oder VB) ansehe, dann bekomme ich das kalte Grausen über so viel Unwissenheit oder auch Ignoranz. Hier mal ein paar Auszüge:

  • PHP ist von der Performance her nicht konkurrenzfähig mit vorkompilierten Assemblies aus .NET. PHP selbst wird diesbzgl. sogar überholt von seinen Perl oder Pyton-Kollegen. Auch Erweiterungen wie Zend Optimizer etc. können das Loch da nicht stopfen. (hier)
  • Web-Anwendungen erfordern deutlich erhöhten Aufwand, im Gegensatz zu Windows Forms Anwendungen. (hier)
  • Um eine gewisse Usability zu erreichen, muss in Web-Anwendungen deutlich mehr Aufwand getrieben werden, als zum Beispiel in eine Windows Forms Anwendung. (hier)
  • Die ‘Usability’ sollte IMHO entscheidend sein! Heute akzeptieren leider viel zu viele Anwender noch jeden Unsinn im Web-Browser. Ich hoffe, dass sich dies bald mal bessern wird und die Programmierer+Anwender ‘reifer’ werden. (hier)
  • Heute sind zu viele ‘Kunden’ entweder (noch) inkompetent oder vom Web ‘geblendet’ (hier)
  • Bei ernsthafter Betrachtung fallen aber heute IMHO 99% aller Web-Sites schlicht durch. (hier)
  • Ein Web/Browser ist IMHO brauchbar um zB ganz rasch ein paar diverse Informationsquellen durchzusurfen/filtern. Aber überall wo man länger interaktiv & produktiv mit _arbeiten_ soll, gehören _native_ Apps hin, ohne den Browser-Quatsch drum herum. (hier)
  • Es ist ein Armutszeugins aktueller Informatik sondergleichen, wenn die Leistungsfähigkeit heutiger PCs wegen HTML/Browser usw völlig brach liegt. (hier)

Aua, das tut wirklich weh. Die Diskussion läuft übrigens noch – hier als Webversion, hier als Newsgruppen-Version und wer will kanns auch hier im Form besprechen.

ME TOO: Microsoft veröffentlicht Quellcode vom NET-Framework

Wie jeder andere .NET-Entwickler, der ein Blog bedienen kann, muss auch ich erwähnen: Der Kollege Guthrie hat erklärt, dass der Quälcode Quellkot Quellcode vom .NET-Framework jetzt verfügbar ist.

Musste man ja mal sagen.

BRAINDUMP: Zeilenänderungen im WindowsForms-DataGridView erkennen und speichern

Gerade eben hab ich mir nen Wolf gesucht. Situation: DataGridView in Windows-Forms-Applikation auf das Formular gezogen, per automatisch generierter Datenquelle an Datenbank gebunden – und dann der untaugliche Versuch, die Daten nach dem Editieren im DataGridView gepflegt wieder wegspeichern zu lassen. Untauglich deshalb, da es keine geeigneten Events gibt – weder auf Ebene der DataTable, noch des DataGridViews, noch der Datenquelle.

Nach ein wenig rumgooglen hab ich eine Lösung gefunden: An das PositionChanged-Ereignis der BindingSource binden und dort dann die jeweils letzte Zeile speichern. Könnte dann so aussehen:

private DataRow _lastRow;

public DataRow LastRow
{
   get { return _lastRow; }
   set { _lastRow = value; }
}

///

/// Wird aufgerufen, wenn sich die Position
/// des Cursors geändert hat
///

private void BindingSource_PositionChanged(
   object sender, EventArgs e)
{
   // Bindingsource herausfinden
   BindingSource source = ((BindingSource)sender);

   // Aktuelle Zeile herausfinden
   DataRow currentRow = ((DataRowView)source.Current).Row;

   // Mit letzter Zeile vergleichen
   if (currentRow.Equals(LastRow))
   {
      return;
   }

   // Daten aktualisieren
   if (null != LastRow)
   {
      if (LastRow.RowState == DataRowState.Modified)
      {
          // Updaten der Daten
          // …
      }
      else if (LastRow.RowState == DataRowState.Added)
      {
         // Hinzufügen der Daten
         // …
      }
   }

   // Aktuelle Spalte merken
   LastRow = currentRow;
}

Also, irgendwie nervt mich das. Ich werd wohl wieder auf das Click- bzw. DoubleClick-Ereignis des DataGridViews reagieren und dann den aktuellen Datensatz in einem neuen Fenster anzeigen und bearbeiten lassen. Macht sich alles deutlich einfacher.

DOTNET: #region-Prä-Prozessor-Direktive mit Überraschungseffekt

Echt, man wird alt wie Sack und lernt doch immer wieder was dazu, wenn man eine Schulung gibt. So geschehen in dieser Woche, als ich eine C#-Schulung für C++-Umsteiger gegeben habe.

Speziell ging es um die #region-Direktive, mit deren Hilfe man Bereiche im Code zusammenklappen und benennen kann. Ich zeige das wie gewohnt für Klassen und Methoden und hab nicht schlecht aus der Wäsche geschaut, als plötzlich die Frage aufkam, ob das nicht auch innerhalb von Methoden oder Eigenschaften ginge. Öhm, eigentlich, … Okay, ausprobiert, geschaut – und es hat funktioniert. Logisch, ist ja eine Prä-Prozessor-Direktive.

Ist mir aber in dem Moment nicht eingefallen. Nun weiß ich es aber. :-)

DOTNET: Letzter!

Huch, nicht das jemand denkt, ich hätte den .NET 3.5-Start und das VS 2008-Release verpasst. Vor lauter Mac-erei. Nix da. Ich hab nur nix darüber gebloggt, weil man sich dem sowieso nicht entziehen kann.

Aber, und deshalb schreibe ich diesen Beitrag überhaupt, die Kollegen aus Redmond haben wirklich dazu gelernt, denn die Downloads flutschen nur so (ja, liebe CallBoys, sowas geht auch elektronisch): Der komplette knapp 4 GB große Download war bei mir in weniger als 90 Minuten auf der Kiste.

Ich liebe 16 MBit. :-)

BRAINDUMP: Patterns für die Ausgabe von Zeichenketten

Hier findet man eine brauchbare Übersicht über die möglichen Patterns der DateTime.ToString()-Methode.

Nächste Seite »