code it

Martins Tech Blog

Request Validation an eigene Bedürfnisse anpassen

Die Validierung der an die Webanwendung übertragenen Daten spielt eine zentrale Rolle, ist sie doch der Garant dafür, dass kein schadhafter Code eingeschleust werden kann und verhindert Cross Site Scripting (XSS). Und wer hat nicht schon einmal den YOSD gesehen, der erscheint, wenn ein Wert eingegeben wird, der auch nur im entferntesten wie HTML aussieht.

Was hier passiert ist klar. Die Request Validation von ASP.NET hat in den Eingabewerten einen Eintrag gefunden, den sie als potenziell unsicher erkannt hat und schützt nun die Anwendung indem eine HttpRequestValidationException geworfen wird. Was für Entwickler an der Request Validierung störend ist, ist, dass sie an einer Stelle vorgenommen wird, an der nicht so richtig gut eingegriffen werden kann - sie geschieht vor der BeginRequest-Phase des Http Requests. In meinem Post zum Attribut AllowHtml habe ich schon gezeigt, wie man mit Hilfe des AllowHtmlAttribute an Eigenschaften des Models die Request Validierung für einzelne Feldwerte deaktivieren kann. Aber ASP.NET bietet noch eine weitere Möglichkeit, in die Validierung einzugreifen: eine eigene Implementierung der Request Validation. Mehr...

FileNotFoundException im Konstruktor von SPSite

Als ich eben den Post zu AddFieldAsXml geschrieben habe, habe ich die Beispiele in einer Konsolenanwendung nachgestellt. Das Beispiel ist nicht sehr kompliziert und doch überraschte mich SharePoint direkt in der ersten Zeile mit einer FileNotFoundException.

Laut MSDN wird eine FileNotFoundException im SPSite-Konstruktor dann geworfen, wenn die SiteCollection unter der Url nicht gefunden werden konnte. Die Url ließ sich jedoch problemlos im Browser aufrufen - sowohl unter localhost als auch unter dem Rechnernamen wurde die SiteCollection gefunden. Nächster Versuch war also, die Credentials explizit mitzugeben, auch wenn das eigentlich nicht das Problem sein dürfte, weil sowohl Browser als auch Visual Studio mit dem gleichen Nutzeraccount verwendet wurden.

Was die MSDN-Seite verschweigt: Diese Exception wird auch dann geworfen, wenn das Platform-Target nicht stimmt. Konsolenanwendungen haben in der Visual Studio Vorlage grundsätzlich erst einmal das Platform Target x86; SharePoint aber ist eine 64-bit-Anwendung, daher muss das Platform Target der Konsolenanwendung auch x64 sein, damit es funktioniert. Also die Einstellung eben geändert, neu kompiliert und schon klappt es auch von der Konsole.

AddFieldAsXml - Bug oder Feature, das ist hier die Frage

Möchte man programmatisch Felder zu einer SharePoint-Liste hinzufügen, so stolpert man früher oder später über die Methoden Add bzw. AddFieldAsXml von SPFieldCollection. In vielen Fällen führen beide Methoden zum Ziel, wobei AddFieldAsXml den Vorteil hat, dass man recht einfach das Schema-Xml aus bereits bestehenden Listen übernehmen kann, es auch recht komplexe Szenarien zulässt und dann in meinen Augen besser lesbar ist als Quellcode, der über mehrere Zeilen oder Klassen verteilt versucht, ein Objekt vom Typ SPField zu erzeugen und mit den passenden Eigenschaften zu bestücken.

Damit möchte ich auch schon zum Problem kommen: Ein Objekt vom Typ SPField verfügt über zwei Eigenschaften, die in diesem Kontext eine Rolle spielen: Title stellt in der Liste den Spaltennamen dar, den der Nutzer zu Gesicht bekommt. Dieser kann natürlich auch Leerzeichen oder Sonderzeichen beinhalten. Im Gegensatz dazu ist InternalName die interne Referenz auf die Spalte und als solche ist sie in der jeweiligen Liste eindeutig und readonly. Zudem werden hier sämtliche Sonderzeichen maskiert dargestellt. Um es mal plastisch darzustellen im folgenden das Beispiel aus der MSDN.

using (var site = new SPSite("http://localhost/"))
{
    using (var web = site.RootWeb)
    {
            var list = web.Lists["Shared Documents"];

            var displayName = "My Custom Field";
            var staticName = "MyStaticName";

            var strInternalName = list.Fields.Add(displayName, SPFieldType.Text, false);
            var field = list.Fields.GetFieldByInternalName(strInternalName);

            field.StaticName = staticName;
            field.Update();
    }
}

In diesem Fall ist Title nun wie erwartet My Custom Field und der Internal Name My_x0020_Custom_x0020_Field.

Wie bereits angedeutet, geht das ganze auch mit AddFieldAsXml:

using (var site = new SPSite("http://localhost/"))
{
    using (var web = site.RootWeb)
    {
        var list = web.Lists["Shared Documents"];

        var fieldXml = "<Field Type=\"Text\" MaxLength=\"50\" DisplayName=\"My Custom Field\" ID=\"15C0D97B-3735-4AD9-8FE2-8A01E5B43486\" StaticName=\"MyStaticName\" Name=\"MyInternalName\" />";
        var strInternalName = list.Fields.AddFieldAsXml(fieldXml);

        var field = list.Fields.GetFieldByInternalName(strInternalName);

        field.Update();

    }
}

Was bei der Ausführung nun auffällt: Obwohl ich im XML einen Internal Name vorgegeben habe, wird dieser nicht verwendet, sondern weiterhin auf Basis des Display Name ein Internal Name generiert. Das ist insofern schlecht, da wie beschrieben der Internal Name ja der Identifier der Spalte ist und deshalb auch in CAML-Queries verwendet wird. Mehr...

INotifyPropertyChanged ohne Magic Strings mit Prism 4

Wer mit WPF zu tun hat, kennt das: Damit die Benutzeroberfläche richtig aktualisiert wird, müssen die ViewModels INotifyPropertyChanged implementieren und das große Problem dabei ist, dass die PropertyChangedEventArgs den Namen der Eigenschaft als String erwarten. Wenn man sich konzentriert, ist das initial auch nicht allzu schwierig. Problematisch kann es dann werden, wenn man Refactorings durchführt und dann vergisst, den String anzupassen.

using System.ComponentModel;

public class MainViewModel : INotifyPropertyChanged
{
    private string zahl1;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Zahl1
    {
        get
        {
            return this.zahl1;
        }

        set
        {
            this.zahl1 = value;
            this.RaisePropertyChanged("Zahl1");
        }
    }

    private void RaisePropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Dieses Problem kann man sehr elegant mit AOP umgehen - zum Beispiel mit einem Aspekt mit PostSharp. Diese Lösung finde ich persönlich sehr elegant, jedoch kann oder möchte man dieses Produkt nicht immer einsetzen. Deshalb möchte ich hier kurz auf eine andere Alternative hinweisen. Mehr...

Neue Version von "Sag's anders" released

Vor etwa einem Jahr ging meine App Sag's anders im Windows Phone Marketplace live. Die Idee dazu kam mir, als ich zufällig bei der Suche nach einem Synonym über das Projekt OpenThesaurus gestolpert bin.

Was ist OpenThesaurus?

OpenThesaurus ist ein freies deutsches Synonymwörterbuch. Das bedeutet in erster Linie, dass man hier Wörter gleicher oder ähnlicher Bedeutung nachschlagen kann. Aber Nachschlagen ist nicht alles. Ähnlich einem Wiki kann man sich auf der Webseite auch anmelden und selbst Synonyme eintragen.

Was ist Sag's anders?

Sag's anders ist ein Windows Phone 7 Client, der die API von OpenThesaurus anbindet. Damit kann man nun direkt im Phone eine Synonymsuche anstoßen. Leider bietet die API von OpenThesaurus keinen schreibenden Zugriff, so dass man zum Eintragen von Synonymen weiterhin über die Webseite gehen muss.

Seit über einem Jahr nun ist Sag's anders im Windows Phone Marketplace vertreten und war damit eine der ersten Apps. Inzwischen wurde die App über 28.000 mal heruntergeladen. Was als Lernprojekt für eine neue Technologie begann ist damit über die Zeit ein voller Erfolg geworden. Inzwischen ist die Zeit für ein Update gekommen - und was soll ich sagen: Sag's anders Version 1.2 ist jetzt im Marketplace verfügbar.

Und abgesehen vom Upgrade der Frameworkversion von Windows Phone 7 zu Windows Phone 7.1 gibt es auch einige neue Features:

  • Nachschlagen: Wurde eine Synonymsuche durchgeführt, kann das Suchwort in der deutschsprachigen Wikipedia-Seite nachgeschlagen werden.
  • Weitersuchen: Ein gefundenes Synonym kann markiert und durch Aktivieren von Weitersuchen als neues Suchwort übernommen werden.
  • Kopieren: Nicht immer möchte man sich nur in der Anwendung aufhalten, sondern braucht das Wort vielleicht, weil man gerade einen Text schreibt. Das gefundene Synonym kann in die Zwischenablage kopiert und in der Zielanwendung wieder eingefügt werden.

Sag's anders bleibt nach wie vor eine kostenlose App. Sie kann im Marketplace heruntergeladen werden (Deeplink) - befindet sich die Anwendung im Hochformat wird lediglich am unteren Bildschirmrand ein kleines Werbebanner eingeblendet.

SQL Server 2012 und neue analytische Funktionen

Dieser Post zum Thema Neuerungen im SQL Server 2012 widmet sich einigen neu hinzugekommenen analytischen Funktionen. Damit sind Auswertungen der Datenbestände möglich. Und ganz besonders zum Jahresende sind auch Performanceberichte und Vorjahresvergleiche immer von besonderer Bedeutung. Aus diesem Grund möchte ich mich heute etwas näher damit beschäftigen, wie man aktuelle Zahlen - seien es nun Verkaufszahlen, Umsatzzahlen, Gewinne, Arbeitslosenanzahl oder was auch immer mit anderen Zeiträumen vergleicht.

Der SQL Server 2012 bietet hier ein paar wesentliche Verbesserungen, dass sich der Datenbankentwickler nicht mehr zwingend die Finger brechen muss um mit Verrenkungen an diese Werte zu kommen. Mit Hilfe zweier neuer Funktionen und einer neuen Klausel sind Vorzeitraum- bzw. Folgezeitraumbetrachtungen oder Auswertungen mit einem gleitenden Fenster kein Problem mehr. Schauen wir uns das gemeinsam an einigen Beispielen an:

Mehr...

SQL Server 2012 und neue Datumsfunktionen

Über KonvertierungsfunktionenFormatierungsmöglichkeiten und Logikfunktionen in SQL Server 2012 habe ich schon berichtet. Heute soll es um neue Datumsfunktionen gehen.

*FROMPARTS-Funktionen

Eine Möglichkeit Datums- und Zeitwerte zu erstellen ist, die Werte aus Textwerten zu parsen bzw. zu konvertieren  (z.B. mit PARSE oder mit CONVERT). Wenn der Wert nur als Text zur Verfügung steht, dann ist das durchaus ein akzeptabler Weg. In der Vergangenheit wurde dieser Weg jedoch auch häufig gewählt, wenn man Datumsteile vorliegen hatte. Oft wurde dann ein Text im passenden Format per Stringverkettung erstellt und dieser dann mit Hilfe von CONVERT in einen Datumswert konvertiert. Dieser Weg ist aber recht umständlich und fehleranfällig, da die Culture beim Format eine wesentliche Rolle spielt.

Neu im SQL Server 2012 ist sind nun Funktionen wie DATEFROMPARTS oder DATETIMEFROMPARTS, in der man Datumsfraktale angeben kann und hier typsicherer Datumswerte erzeugen kann.

DECLARE @year int, @month int, @day int;
SET @year = 2012;
SET @month = 1;
SET @day = 12;

-- alt
SELECT CONVERT(datetime, LTRIM(@day) + '.' + LTRIM(@month) + '.' + LTRIM(@year), 104)
-- neu
SELECT DATETIMEFROMPARTS(@year, @month, @day, 0, 0, 0, 0)

Genau wie DATETIMEFROMPARTS gibt es Funktionen für so ziemlich alle Datums- und Zeitdatentypen des SQL Servers.

EOMONTH 

Welches Datum hat der Monatsletzte? Besonders wenn man Zeitraumberechnungen oder Ultimoberechnungen durchführen möchte, spielt diese Frage eine relevante Rolle. Wie löst man dieses Problem bisher?

DECLARE @currentdate datetime = GETDATE();
SELECT DATEADD(dd,-DATEPART(dd,DATEADD(MM,1,@currentdate)),DATEADD(MM,1,@currentdate))

Man addiert einen Monat zum aktuellen Datum hinzu und zieht dann die Anzahl von Tagen ab, die im aktuellen Monat schon vergangen sind.

Damit man nicht mehr solche Verrenkungen machen muss, gibt es nun die Funktion EOMONTH, die den Monatsletzten des Monats ermittelt.

SELECT EOMONTH(GETDATE())

Wer mag, kann im zweiten Parameter noch einen zusätzlichen Offset hinzufügen, um so zum Beispiel den Monatsletzten des Folgemonats zu ermitteln.

SQL Server 2012 und neue Konvertierungsfuktionen

Mit SQL Server 2012 kommen auch neue Konvertierungsmöglichkeiten hinzu. Bisher bekannt sind ja schon die Möglichkeiten CAST und CONVERT.

TRY_CONVERT

CONVERT führt zu einem Fehler, wenn die Konvertierung nicht funktioniert. Kleines Beispiel gefällig?

SELECT ISNUMERIC('1e1')
SELECT CONVERT(int, '1e1')

Obwohl ISNUMERIC den Wert 1 zurückliefert, schlägt der Aufruf von CONVERT fehl. Eine Konvertierung in den float-Datentyp hingegen wäre erfolgreich. Hier hilft TRY_CONVERT.

SELECT TRY_CONVERT(int, '100')
SELECT TRY_CONVERT(int, '1e1')

Diese Funktion gibt NULL zurück, wenn die Konvertierung nicht durchgeführt werden kann, oder im Erfolgsfall eben den Wert.

PARSE und TRY_PARSE

Um Datums- oder Zahlenwerte aus einem Text in die jeweiligen Datentypen zu konvertieren gibt es nun die Funktionen PARSE und TRY_PARSE. Unterschied zwischen beiden Funktionen ist - ähnlich wie bei CONVERT und TRY_CONVERT auch, dass PARSE eine Exception wirft, wenn die Konvertierung nicht durchgeführt werden kann, TRY_PARSE hingegen NULL.

SELECT TRY_PARSE('01.02.2012' AS datetime USING 'de-DE')
SELECT PARSE('01.02.2012' AS datetime USING 'en-US')
SELECT PARSE('1234567' AS datetime USING 'en-US')
SELECT TRY_PARSE('1234567' AS datetime USING 'en-US')

Wird keine Culture angegeben, so verwendet PARSE die Culture, die mit Hilfe von SET LANGUAGE gesetzt wurde. Sowohl PARSE als auch TRY_PARSE basieren auf der CLR, was zur Folge hat, dass alle Cultures angegeben werden können, die die CLR kennt - nicht nur jene, die dem SQL Server bekannt sind.

SQL Server 2012 und neue Logikfunktionen

Der SQL-Server 2012 bringt unter anderem auch ein paar neue Funktionen mit sich, die im SQL verwendet werden können. Über die Format-Funktion habe ich ja bereits berichtet. Heute soll es um neue Logik-Funktionen gehen.

IIF (Inline IF)

Vor vielen Jahren als ich von Access zu SQL-Server gewechselt bin war es eine ziemliche Umstellung, dass ich die von dort bekannte IIF-Funktion nicht mehr hatte und alle Abfragen angepasst werden mussten. Klar konnte man mit ähnlichen Konstrukten das gleiche erreichen.

SELECT LastName, FirstName, 
CASE WHEN Gender = 1 THEN 'männlich' ELSE 'weiblich' END Gender 
FROM Person

Solche einfachen CASE-WHEN-Konstrukte kann man jetzt (etwas) kürzer schreiben, indem man die IIF-Funktion verwendet.

SELECT LastName, FirstName, 
IIF(Gender = 1, 'männlich', 'weiblich') Gender 
FROM Person

Der Unterschied in der Syntax ist marginal, kann aber dazu führen, dass die Statements einfacher zu lesen sind - besonders dann wenn man die Argumente schachtelt kommt man in meinen Augen bei der Klammern-Schreibweise weniger schnell durcheinander als beim Suchen der Wörter CASE, WHEN, THEN und des jeweils passenden END. Bei der Ausführung wird IIF zu CASE WHEN übersetzt - es handelt sich hier also um eine Art von syntactic sugar. Daraus abgeleitet erlaubt IIF ebenso wie CASE WHEN eine Verschachtelungstiefe von 10.

CHOOSE

Ebenso wie IIF sollte auch CHOOSE allen Access-Jüngern bekannt vorkommen. CHOOSE ermöglicht es, indexbasiert aus Werten auszuwählen. Dazu zunächst ein Beispiel in der bisherigen Syntax:

SELECT LastName, FirstName, 
CASE MaritalStatus 
WHEN 1 THEN 'ledig'
WHEN 2 THEN 'verheiratet'
WHEN 3 THEN 'geschieden'
WHEN 4 THEN 'verwitwet'
WHEN 5 THEN 'getrennt lebend'
WHEN 6 THEN 'verpartnert'
ELSE NULL
END MaritalStatus 
FROM Person

Dieses Konstrukt kann mit CHOOSE anders geschrieben werden:

SELECT LastName, FirstName, 
CHOOSE(MaritalStatus, 'ledig', 'verheiratet', 'geschieden', 'verwitwet', 'getrennt lebend', 'verpartnert') MaritalStatus 
FROM Person

Wichtige Anmerkung für alle C#-Entwickler... der Index ist 1-basiert.

SQL Server 2012 und Contained Databases

Ein sehr spannendes Feature von SQL Server 2012 sind Contained Databases. Dieses Feature ist besonders auf Hosting-Umgebungen ausgerichtet, erlaubt es doch eine bessere Trennung der jeweiligen Datenbank- und damit Anwendungskontexte als es bisher der Fall war.

Was bedeutet das nun de facto? Um bisher einen Nutzer auf einer Datenbank zuzulassen, legte man einen Server-Login an und ließ diesen Nutzer dann auf der jeweiligen Datenbank zu. Das ist ohne Frage auch durchaus sinnvoll, wenn man in Unternehmensgrenzen denkt. Und mal abgesehen von Recovery-Szenarien, bei denen es in der Vergangenheit häufig zu verwaisten Logins und/oder verwaisten Nutzern kam, ist an diesem Ansatz auch nichts auszusetzen. Er ist dann aber hinderlich, wenn man bedenkt, dass man auf einem SQL-Server die Datenbanken völlig voneinander unabhängiger Anwendungen hostet und sicherstellen möchte, dass die dort zugelassenen Nutzer keinesfalls Zugriff auf andere Datenbanken bekommen. Nun unterstelle ich mal, dass Microsoft dieses Feature nicht nur macht, weil es Entwicklern von Webanwendungen und Hostern entgegenkommt, sondern weil Microsoft dieses Feature selbst für Azure auch ganz gut brauchen kann.

Mehr...