code it

Martins Tech Blog

Attribute in Metadaten prüfen

Heute wurde mir eine gute Frage zu einem Problem gestellt, das ich bisher als trivial abgetan habe: Wie ermittle ich in ASP.NET für eine Eigenschaft des Models, ob dort ein bestimmtes Attribut gesetzt ist? Solche Attribute (insbesondere die vom Typ ValidationAttribute) werden ja verwendet, um Eingabevalidierung vorzunehmen.

Einstiegspunkt soll eine ExtensionMethod auf HtmlHelper sein, wie man sie in ASP.NET häufig findet und die einfach nur einen Text in der Html-Seite ausgeben soll.
public static MvcHtmlString RequiredMark<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
{
    var required = expression.IsRequired();
    return new MvcHtmlString((!required ? "kein " : string.Empty) + "Pflichtfeld");
}
Spannender Teil daran ist jetzt die schon verwendete ExtensionMethod IsRequired, die für die eigentliche Magie zuständig ist. Diese ist auch noch recht trivial: Sie prüft die Eingabe und ruft die noch zu erstellende Methode HasAttribute auf, die dann die eigentliche Prüfung auf das gesuchte Attribute durchführt:
private static bool IsRequired<T, V>(this Expression<Func<T, V>> expression)
{
    var memberExpression = expression.Body as MemberExpression;
    if (memberExpression == null)
        throw new InvalidOperationException("Expression must be a member expression");

    return HasAttribute(memberExpression.Member, typeof(RequiredAttribute));
}
Auch diese Methode ist recht einfach implementiert - schließlich gibt es in der Klasse Attribute eine statische Methode IsDefined, die dafür verwendet werden kann:
private static bool HasAttribute([NotNull] MemberInfo memberInfo, [NotNull] Type attributeType)
{
    if (memberInfo == null)
    {
        throw new ArgumentNullException();
    }

    var isDefinedOnMember = Attribute.IsDefined(memberInfo, attributeType);

    return isDefinedOnMember;
}
Funktioniert super ..... Funktioniert super und deswegen hören hier die meisten Lösungsvorschläge in einschlägigen Foren auch schon auf.... Funktioniert super, so lange man das Attribut direkt auf dem Model definiert. Allerdings gibt es auch die Möglichkeit, Metadatentypen zu definieren, die dann die Attribute beinhalten. Das ist immer dann sinnvoll, wenn das eigentliche Modell automatisch generiert wird - aus einem Designer oder aus einem T4-Template.

Schauen wir uns ein Beispiel an:
public class MyViewModel
{
    [Required]
    public string Name { get; set; }
}
Dafür funktioniert die bisher erstellte Lösung. Aber bei dem folgenden Beispiel schlägt unsere Prüfung fehl:
[MetadataType(typeof(MyViewModelMetaData))]
public class MyViewModel
{
    public string Name { get; set; }
}

public class MyViewModelMetaData
{
    [Required]
    public string Name { get; set; }
}

Wie kann das Problem gelöst werden? Ganz einfach - zusätzlich zu der schon erstellten Prüfung auf direkt gesetzte Attribute muss nun noch dem Metadatenattribut auf dem Typ gefolgt werden und in diesem Typ geprüft werden welche Attribute auf dem gleichnamigen Member gesetzt sind:
private static bool HasAttribute([NotNull] MemberInfo memberInfo, [NotNull] Type attributeType)
{
    if (memberInfo == null)
    {
        throw new ArgumentNullException();
    }

    // hier prüfe ich direkt gesetzte Attribute
    var isDefinedOnMember = Attribute.IsDefined(memberInfo, attributeType);
    if (isDefinedOnMember)
    {
        return true;
    }

    // jetzt wird noch der Metadatentyp geprüft
    var type = GetMetadataType(memberInfo);
    if (type == null)
    {
        return false;
    }

           
    return type.GetProperties().Any(prop => prop.Name.Equals(memberInfo.Name, StringComparison.OrdinalIgnoreCase) && Attribute.IsDefined(prop, attributeType));
}
Nun liefert die erstellte Methode auch in diesem Fall korrekte Ergebnisse.

Happy coding.

SetWindowHookEx - cool oder nur Fingerübung?

Die Anforderung ist recht schnell erklärt: Erkenne, wann ein Fenster einer Anwendung maximiert, minimiert oder wiederhergestellt wird. "Das kann doch so schwer nicht sein" denkt man sich da, schließlich wirft Windows ja nur so mit Nachrichten um sich, die man einfach nur abfangen muss. Die Windows-API ist dein Freund. Ok, ganz so einfach ist es dann doch nicht, sonst bräuchte dieser Blogpost ja nicht geschrieben werden.

Damit das Fensterhandling und auch so ziemlich alles andere in Windows funktioniert, werden Nachrichten an Fenster gesendet. Zumeist macht Windows das selbst (z.B. Fenster 358: Maximiere dich"). Kommt diese Nachricht am Fenster an, gibt es dort eine Kette von "Abonnenten" - die hook chain. Und in diese Kette kann man eigene Funktionen eingliedern lassen, die dann Teil dieser Kette werden.

Mehr...

Field oder Property, das ist hier die Frage

Manchmal ergeben sich im persönlichen Gespräch Grundsatzdiskussionen über Programmierstile und Herangehensweisen und so ist es auch in diesem Beispiel, welches sich erst kürzlich zugetragen hat. Die Frage ist: Auto-Property oder öffentliches Feld - welcher Variante ist der Vorrang zu geben?

Möchte man Daten aus einer Klasse nach außen freigeben, so ist die einfachste Möglichkeit, einfach ein Feld zu deklarieren und diesem den Zugriffsmodifizierer public zu verpassen:

public class Person
{
    public string LastName;
    public string FirstName;
}

Diese Möglichkeit gibt es, Sie widerspricht aber dem Konzept der Kapselung. Datenzugriffe mit Eigenschaften zu kapseln bietet zum einen die Möglichkeit einen dedizierten Zugriff auf private Informationen des Objektes zu gewährleisten und vielleicht auch noch eine Validierung der Daten vorzunehmen, die dann gespeichert werden sollen. Im Standardfall sieht das Ganze so aus:

private string _name;
public string Name
{
    get { return _name; }
    set { _name = value; }
}

Damit gibt es ein privates Feld, das über die Eigenschaft nach außen verfügbar gemacht wird und im Setter können dann Prüfungen durchgeführt werden.

Seit C# 3.0 gibt es nun die Möglichkeit, autoimplemented Properties zu verwenden:

public string Name { get; set; }

Damit ist noch immer eine Eigenschaft vorhanden, jedoch ist die Anlage des sogenannten Backing Fields damit nicht mehr notwendig - das übernimmt der Compiler. Was damit auch entfällt, ist die Möglichkeit zusätzliche Prüfungen im Setter hinzuzufügen.

Dieser Logik folgend, stellt sich nun aber die Frage: Warum sollte ich autoimplemented Properties verwenden und nicht public Fields (wie im ersten Beispiel). Die eben genannten offensichtlichen Vorteile der Eigenschaften entfallen bei autoimplemented Properties ja.

Grund 1: Asymmetrische Sichtbarkeit 

Eigenschaften erlauben die Definition asymmetrischer Sichtbarkeit von Getter und Setter:

public string Name { get; private set; }
public string DateOfBirth { private get; set; }

Ein Feld ist immer komplett public oder komplett private, nicht aber private beschreibbar und öffentlich schreibgeschützt.

Grund 2: Definition in Interfaces

Eigenschaften können in Interfaces festgeschrieben werden, während das für Felder nicht möglich ist.

public interface IPerson
{
    string LastName { get; set; }
    string FirstName { get; set; }
}

public class Student : IPerson
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Bei der Verwendung von Feldern kann man nicht sicherstellen, dass alle Objekte vom Typ Student über öffentlich verfügbare Felder für Nachname und Vorname verfügen.

Grund 3: Überschreibbarkeit

Felder können nicht überschrieben werden. Während man bei Eigenschaften problemlos in Basisklassen eine Deklaration mit virtual oder abstract vornehmen kann, um sicherzustellen, dass ein bestimmter Wert immer vorhanden ist, und die Initialisierung dann ggf. aber doch an eine spezialisiertere Klasse zu delegieren, so ist das mit Feldern nicht möglich.

Grund 4: Abwärtskompatibilität

"Dieser Code wird sich nie ändern" gibt es nicht und auch an der noch so undenkbaren Stelle wird irgendwann einmal der Zeitpunkt gekommen sein, an dem irgendeine Änderung notwendig ist. Und dann ist es gut gewappnet zu sein und eben nicht auf das Feld angewiesen zu sein, sondern die Auto-Property vielleicht aufzubrechen und hier noch zusätzliche Dinge zu machen. Der große Vorteil dann: Die Schnittstelle nach außen muss sich vielleicht nicht zwingend ändern und bereits bestehender Code der die Komponente verwendet, kann in gleicher Art und Weise weiter laufen ohne breaking changes.

Grund 5: Data Binding

Möchte man früher oder später die Objekte zur Datenbindung in WPF verwenden, so benötigt man Properties. Klar ist in diesem Fall keine Änderungsbenachrichtigung möglich - es sei denn man verwendet Aspekte, aber verwendbar sind sie so schon.

Wahrscheinlich gibt es noch den ein oder anderen weiteren Grund. Ich denke, die obige Liste bietet noch Platz für Erweiterungen.

RSS-Feeds an eigene Bedürfnisse anpassen

In meinem letzten Post hab ich eine kleine Einführung in das Thema RSS-Feeds mit .NET erzeugen gegeben. Nun sind aber nicht alle Inhalte, die man per RSS-Feed übertragen möchte in den Standard-Elementen unterzubringen, die die RSS-Spezifikation definiert. Typische Beispiele dafür sind zusätzliche Geodaten oder Daten zu Medieninhalten in Podcasts. Um auch dieser Anforderung gerecht zu werden, bieten die beiden vorgestellten Klassen SyndicationFeed und SyndicationItem noch die Möglichkeit eigene Attribute oder eigene Elemente zu definieren. Wie das geht, soll an einem kleinen Beispiel gezeigt werden:

Eigene Elemente zum Item hinzufügen

Möchte man einfach nur zusätzliche Elemente zu den Items hinzufügen, kommt man recht schnell ans Ziel. Schlüssel sind die ElementExtensions. Im Beispiel werden jedem Item noch die Elemente Latitude und Longitude zur Definition einer Geokoordinate hinzugefügt.

var item = new SyndicationItem
{
    Id = "http://myfeeds/2011/06/" + i,
    Title = SyndicationContent.CreatePlaintextContent("Wichtige Mitteilung"),
};

item.ElementExtensions.Add("Latitude", null, 34.2543);
item.ElementExtensions.Add("Longitude", null, 14.2545);

Das Resultat ist, dass wie gewünscht das Item-Element über die definierten Sub-Elemente verfügt.

<item>
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <Latitude>34.2543</Latitude>
    <Longitude>14.2545</Longitude>
</item>

Den gleichen Weg kann man dann auch beim Einlesen der Daten wieder verwenden: In der Auflistung ElementExtensions befinden sich die hinzugefügten Elemente mit den jeweiligen Namen und Inhalten. Mit Hilfe der generischen Methode GetObject kann man dann auf die Inhalte zugreifen.

var feed2 = SyndicationFeed.Load(reader);
foreach (var item in feed2.Items)
{
    var latitudeElementExtension = item
        .ElementExtensions
        .Single(e => e.OuterName == "Latitude");
    var latitude = latitudeElementExtension.GetObject<double>();
}

Analog zu den ElementExtensions gibt es die AttributeExtensions, die (wie sollte es anders sein) keine Elemente sondern Attribute erzeugen.

Eigene komplexe Objekte zum Item hinzufügen

ElementExtensions erlauben nicht nur das Hinzufügen primitiver Datentypen sondern auch komplexer Objekte - Bedingung: Sie müssen xml-serialisierbar sein und es muss ein passender XML-Serializer existieren oder es muss sich um einen DataContract handeln. In meinem Fall definiere ich mir nun eine neue Klasse GeoCoordinate, die sich als Location serialisiert.

[XmlRoot("Location", Namespace = "http://ns.uniquesoftware.de/location")]
[Serializable]
public class GeoCoordinate
{
public GeoCoordinate()
{
}

 public GeoCoordinate(double lat, double lon)
 {
     Latitude = lat;
     Longitude = lon;
 }

 [XmlElement("Latitude")]
 public double Latitude { get; set; }
 [XmlElement("Longitude")]
 public double Longitude { get; set; }
}

Im Anschluss definiere ich wie vorhin auch eine ElementExtension und füge dieser der Auflistung des Elements hinzu.

var item = new SyndicationItem
{
    Id = "http://myfeeds/2011/06/" + i,
    Title = SyndicationContent.CreatePlaintextContent("Wichtige Mitteilung"),
};
item.ElementExtensions.Add(
    new GeoCoordinate(34.2543, 14.2545), 
    new XmlSerializer(typeof(GeoCoordinate)));

Das resultierende XML im Feed beinhaltet nun wie erwartet die eben definierten Tags, die über die definierten Namespaces verfügen.

<item>   
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <Location 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns="http://ns.uniquesoftware.de/location">
        <Latitude>34.2543</Latitude>
        <Longitude>14.2545</Longitude>
    </Location>
</item>

Eigene Namespaces verwenden

Häufig trifft man auf Konstrukte, in denen der Namespace nicht explizit jedes Mal wieder als Attribut im Element definiert wird, sondern wo nur noch eine Namespace-Referenz existiert. Nichts einfacher als das: Dazu fügt man einfach die XML-Namespace-Deklaration der AttributeExtensions-Auflistung des SyndicationFeed-Objektes hinzu. Im folgenden Beispiel habe ich dafür den Namespace "my" deklariert.

// create new syndication feed 
var feed = new SyndicationFeed(
    "Mein Blog Feed",
    "Wissenwertes über ASP.NET, WPF und Silverlight",
    new Uri("http://feeds.feedburner.com/discoveringmicrosofttechnologies"))
    {
        Language = "de-DE",
        Copyright = new TextSyndicationContent("Unique Software"),
        Generator = "Unique Software News Generator"
    };
feed.AttributeExtensions.Add(
    new XmlQualifiedName("my", "http://www.w3.org/2000/xmlns/"), 
    "http://ns.uniquesoftware.de/location");

Der Effekt ist, dass nun der Namespace im XML-Element channel definiert und dann automatisch eine Namespace-Referenz aufgebaut wird.

<item>
    <guid isPermaLink="false">http://myfeeds/2011/06/0</guid>
    <title>Wichtige Mitteilung</title>
    <my:Location>
        <my:Latitude>34.2543</my:Latitude>
        <my:Longitude>14.2545</my:Longitude>
    </my:Location>
</item>

Zusammenfassung

ElementExtensions und AttributeExtensions sind eine gute Möglichkeit, RSS-Feeds flexibel auf die eigenen Bedürfnisse anzupassen und mit Hilfe des Objektmodells typsicher auf die Objekte beinhalteten Objekte zuzugreifen. Besonders bei XML-Strukturen, die nur noch sehr entfernt etwas mit dem ursprünglichen Feed-Objektmodell zu tun haben, wird es wohl immer wieder eine Abwägung von Aufwand und Nutzen sein, ob man ElementExtensions und die Syndication-Objekte verwendet oder ob man die XML-Struktur "schnell eben selbst" per Linq-To-Xml zusammenbaut. Ich persönlich finde die sich bietenden Möglichkeiten wieder recht umfangreich und finde es schade, dass die Klassen so ein unbeachtetes Dasein fristen. 

Englische Version Englische Version

RSS-Inhalte mit .NET erzeugen

Das Erzeugen einer XML-Struktur, die der RSS-Spezifikation entspricht ist gar nicht so kompliziert - erst recht nicht, seitdem man mit Linq-To-Xml recht einfach durch XML-Dokumente navigieren und diese auch modifizieren kann. Nachteil ist: Man muss die Spezifikation immer griffbereit haben, denn ganz schnell kommt man zu Fragen wie: "Wie hieß noch gleich das XML-Element für das Erstellungsdatum und in welchem Format muss es ausgegeben werden?" Was bisher aber nur wenige zu wissen scheinen: So viel Arbeit muss man sich gar nicht mehr machen. Seit dem .NET Framework 3.5 gibt es den Namespace System.ServiceModel.Syndication, dessen Klassen einen Großteil der Arbeit abnehmen. Die Erstellung ist ganz einfach: Zunächst erstellt man sich ein Objekt vom Typ SyndicationFeed. Dieses Objekt repräsentiert den eigentlichen Feed und dessen Eigenschaften.
 
// create new syndication feed  
var feed = new SyndicationFeed("Mein Blog Feed",
 "Wissenwertes über ASP.NET, WPF und Silverlight",
 new Uri("http://feeds.feedburner.com/discoveringmicrosofttechnologies"))
  {
     Language = "de-DE",
     Copyright = new TextSyndicationContent("Unique Software"),
     Generator = "Unique Software News Generator"
  };
 
Die Klasse SyndicationItem ermöglicht es dann, ebenfalls über das Objektmodell, die eigentlichen Einträge zu erzeugen, die mit Hilfe des Feeds übermittelt werden sollen. Diese werden dann dem Feed-Objekt nur noch zugewiesen.
 
// create syndication items which should be delivered
var items = new List<SyndicationItem>();
for (var i = 0; i < 5; i++)
{ 
 var item = new SyndicationItem
     {
         Id = "http://myfeeds/2011/06/" +  i,
         PublishDate = DateTime.Now,
         Title = SyndicationContent
             .CreatePlaintextContent("Wichtige Mitteilung"),
         Content = SyndicationContent
             .CreateHtmlContent("Meine wichtige Nachricht an alle Leser"),
         Summary = SyndicationContent
             .CreatePlaintextContent("Zusammenfassung meiner Mitteilung")
     };
     items.Add(item);
}
feed.Items = items;
 
Das war's auch schon fast: Letzter Schritt ist die Umwandlung in XML. Dafür gibt es auf dem Feed-Objekt die Methoden SaveAsAtom10 und SaveAsRss20, die diese Arbeit übernehmen.
 
var builder = new StringBuilder();
using (var writer = XmlWriter.Create(builder))
{
 feed.SaveAsRss20(writer);
 writer.Flush();
 writer.Close();
}
var xml = builder.ToString();
 
Im Hintergrund kommen hier je nach Methode die Atom10FeedFormatter-Klasse bzw. die Rss20FeedFormatter-Klasse zum Einsatz. Und mit diesem XML kann man nun das gleiche machen, was man sonst mit dem XML gemacht hätte, das man mit Linq-To-Xml zusammengebaut hätte: Man liefert es aus.

Englische Version Englische Version

Lesbare Bytes

Das Problem ist einfach umrissen: Man hat die Größe einer Datei oder (auch gern genommen) eine Größenbeschränkung in Bytes vorliegen und möchte diese dem Benutzer anzeigen. Aber nur die wenigsten Benutzer können mit der Größenangabe 1.048.576 Bytes etwas anfangen. Also möchte man die Größe dem Benutzer in ihm bekannten Größenangaben anzeigen - so wie man es von Windows kennt. Das .NET Framework selbst kennt meines Wissens eine solche Funktion nicht und daher ist es jedem Entwickler selbst überlassen, wie er diese Umrechnung vornimmt.

Auf meiner Suche nach einer Lösung für genau dieses Problem bin ich über einige Lösungsvorschläge bei stackoverflow gestolpert und einen davon fand ich extrem gut, so dass ich dem Problem und möglichen Lösungen diesen Blogpost widmen möchte.

1. Lösung: While-Loop

Dieser Ansatz wird gern genommen und liegt ja in der prozeduralen Denke auch auf der Hand: Man dividiert so lange durch 1024, bis eine Zahl übrig bleibt, die kleiner als 1024 ist. Hat man sich nun noch gemerkt, wie häufig man dies angewandt hat, so weiß man auch was man als Einheit dahinter schreiben muss.

var unit = new[] { "B", "KB", "MB", "GB", "TB", "PB" };
var index = 0;
var value = bytes;

while (value >= 1024)
{
 index++;
 value /= 1024;
}

var readable = string.Format("{0} {1}", value, unit[index]);

 

2. Lösung: Windows bemühen

Wie ich schon einleitend beschrieben habe, kann Windows diese Berechnung ja. Warum also nicht mit unmanaged Code auf die passende Windows-Funktion zugreifen? Die Shlwapi.dll stellt eine entsprechende Funktion bereit, mit der diese Formatierung vorgenommen werden kann.

[DllImport("Shlwapi.dll", CharSet = CharSet.Auto)]
public static extern long StrFormatByteSize(
 long fileSize,
 [MarshalAs(UnmanagedType.LPTStr)] StringBuilder buffer,
 int bufferSize);


var sb = new StringBuilder(11);
StrFormatByteSize(bytes, sb, sb.Capacity);
var readable = sb.ToString();

OK, es ist eine Möglichkeit, aber keine die ich präferieren würde.

 

3. Lösung: Mathematisch

Und nun zu der Lösung, auf die ich zugegebenermaßen nicht gekommen bin, die ich aber sehr charmant finde. Mit Hilfe nur weniger mathematischer Funktionen lässt sich die Schleife aus der Lösung 1 ersetzen. Durch eine geschickte Kombination von Logarithmus-, Potenz- und Rundungsfunktionen lässt sich sowohl der gerundete Wert als auch die anzuzeigende Einheit ermitteln.

var unit = new[] { "B", "KB", "MB", "GB", "TB", "PB" };
var index = Convert.ToInt32(
    Math.Floor(Math.Log(Math.Abs(bytes), 1024)));
var value = Math.Round(bytes / Math.Pow(1024, index), 2);
var readable = string.Format("{0} {1}", value,  unit[index]);

Einziger Wert für den diese Variante nicht funktioniert ist 0 Byte.

Wie ganz häufig, so gibt es auch hier viele Wege, die zum Ziel führen und es bleibt jedem selbst überlassen, welchen Weg er wählt.

Du bekommst mich nicht!

Ich finde ja die Methode Contains auf generischen Listen sehr praktisch. Um so überraschter war ich nun heute, als ich wie selbstverständlich auf eine PointCollection ein Contains aufrufen wollte und mit einer für mich sehr unerwarteten ArgumentException belohnt wurde.

Mein Code ist ganz einfach nachgestellt: Eine PointCollection wird mit 3 Punkten initialisiert, ich hole mir eine Referenz auf den ersten Punkt und verwende dann die Methode Contains, um zu prüfen, ob dieser Punkt in der Liste enthalten ist.

var points = new PointCollection {
   new Point(50, 50),
   new Point(100, 100),
   new Point(150, 150)
};

var point = points[0];

var isInList = points.Contains(point);

Sieht gar nicht so kompliziert aus und sollte eigentlich funktionieren. Aber: PointCollection ist natürlich keine List<Point>, sondern eine PresentationFrameworkCollection<Point>. Trotzdem hätte ich erwartet, dass dieser Typ in der Lage ist, ein enthaltenes Member zu finden.

Abhilfe schafft hier die ExtensionMethod auf IEnumerable mit dem gleichen Namen. Dieser kann als zweiter Parameter ein Comparer übergeben werden. Das bedeutet natürlich zusätzlichen Aufwand - auch wenn dieser hier sehr marginal ist. Aber warum das Rad neu erfinden. Es gibt ja bereits einen Default-Comparer der genau diese Arbeit übernehmen kann. Dazu übergibt man der ExtensionMethod einfach im zweiten Parameter den Wert null.

var points = new PointCollection {
   new Point(50, 50),
   new Point(100, 100),
   new Point(150, 150)
};

var point = new Point(50, 50);

var isInList = points.Contains(point, null);

Keine Fehlermeldung mehr und das Ergebnis stimmt auch.

Zugriff auf WorkItems über das Objektmodell

Mit Hilfe des Objektmodells kann man recht simpel auf die WorkItems im Team Foundation Server zugreifen. Wie das geht, werde ich hier an einem kleinen Beispiel zeigen, in dem alle WorkItems eines TeamProjects aufgelistet werden sollen.

Zunächst werden Referenzen auf die Assemblies Microsoft.TeamFoundation.Client und Microsoft.TeamFoundation.WorkItemTracking.Client benötigt. Dann ist alles recht einfach. Mit den Klassen TfsTeamProjectCollection und WorkItemStore stellt uns das Objektmodell das Handwerkszeug bereit.

private WorkItemCollection GetAllWorkItems()
{
   string teamCollectionUrl = "http://tfs.mydomain:8080/tfs/defaultcollection";
   string projectName = "MyTestProject";

   var projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(teamCollectionUrl));

   var workItemStore = new WorkItemStore(projectCollection);

   var wiq = string.Format("SELECT System.Title, System.Description FROM WorkItems WHERE System.TeamProject = '{0}'", projectName);

   var workItemCollection = workItemStore.Query(wiq);
   return workItemCollection;
}

Mit Hilfe der TfsTeamProjectCollectionFactory und der Url der Project-Collection lässt man sich die TfsTeamProjectCollection geben und kann dort dann eine WIQL-Query absetzen. WIQL steht für Work Item Query Language und ist eine Abfragesprache, die in einer SQL-ähnlichen Syntax das Abfragen von WorkItems erlaubt.

Eine weitere Möglichkeit sich mit der TfsProjectCollection zu verbinden ist der Konstruktor der TfsTeamProjectCollection. Vorteil dieser Methode ist, dass man hier zudem auch die Möglichkeit hat, abweichende Credentials zu hinterlegen.

private WorkItemCollection GetAllWorkItems()
{
   string teamCollectionUrl = "http://tfs.mydomain:8080/tfs/defaultcollection";
   string projectName = "MyTestProject";
   string username = "user";
   string password = "password";
   string domain =  "mydomain";

   var credentials = new NetworkCredential(username, password, domain);
   var projectCollection = new TfsTeamProjectCollection(new Uri(teamCollectionUrl), credentials);

   var workItemStore = new WorkItemStore(projectCollection);

   var wiq = string.Format("SELECT System.Title, System.Description FROM WorkItems WHERE System.TeamProject = '{0}'", projectName);

   var workItemCollection = workItemStore.Query(wiq);
   return workItemCollection;
}

Das ist beispielsweise dann sinnvoll, wenn man sich in einer anderen Domäne befindet als der Team Foundation Server, den man abfragen möchte.

Verwirrendes NotFound

Das Szenario ist recht schnell umrissen: Abstrakt gesehen gibt es einen Webservice: Unter einer Url ist ein Endpunkt registriert, über den per Get-Parameter gesteuert die Daten gefiltert abgerufen werden können. Auf der Client-Seite gibt es eine Windows Phone 7 Anwendung, die mit Hilfe eines asynchronen WebRequest diese Url abfragt und die in der Response enthaltenen Daten deserialisiert und der Anwendung bereitstellt - also alles recht trivial.

Etwas überrascht war ich, als ich bei der Ausführung im Callback beim Aufruf von EndGetResponse dann eine WebException mit der Meldung "NotFound" bekam. Auch die Details der WebException waren nicht aussagekräftiger.

 

Beim Zugriff auf entfernte Inhalte bedeutet NotFound ja eigentlich, dass die angeforderte Ressource nicht da ist - eine Datei, ein Endpunkt oder was auch immer. Meist lässt sich dann auch mit einer Überwachung der http(s)-Anforderungen überprüfen, ob der Server irgendwo einen Statuscode 404 geliefert hat.

Genau das war aber bei mir nicht der Fall. Im Browser war die Url problemlos aufrufbar und lieferte Daten und die von mir an den WebRequest übergebenen Credentials waren auch die gleichen wie die, die ich im Browser eingegeben hatte. Auch die Überwachung des Traffics (wenn ich die Seite im Browser aufrief) zeigte nie einen Http-Statuscode im 400er oder 500er Bereich an.

Was war nun eigentlich das Problem? Ganz einfach: Ein selbst ausgestelltes Zertifikat. Was man im Browser bei bekannten Seiten recht schnell wegklickt, machte hier nun ein so großes Problem, dass der Zugriff ganz unterbunden wurde.

 

Da das Windows Phone 7 auch über keinen Certificate Store verfügt, im man mal eben Zertifikate ablegen kann und so Vertrauensstellungen definiert, war in diesem Fall der einfachere Weg, wieder auf http umzustellen.

Gewünscht hätte ich mir von der API eine aussagekräftigere Fehlermeldung, damit man gleich in der richtigen Richtung suchen kann. Jetzt weiß ich, dass NotFound bedeutet, dass irgend etwas schief gegangen ist.

Mails versenden mit Outlook und Dynamics

Ein altbekanntes Problem, aber eines, das auch in Foren immer wieder gestellt wird ist "Wie kann ich Mails mit Outlook versenden?" - nach Möglichkeit unabhängig von der Outlook-Version des Anwenders.

Ein Weg unter vielen ist die Verwendung der Dynamic Language Runtime. Diese gibt es seit dem .NET Framework 4.0 und sie erlaubt einen Zugriff auf Typen, die in ihrer wirklichen Ausprägung erst zur Laufzeit fest stehen. Einen Überblick über die Grundzüge und die Möglichkeiten der DLR gibt es in der MSDN.

In der Praxis sieht das Ganze dann in etwa so aus:

private void ComposeEmail(string receipient, string subject, string body)
{
   const int olMailItem = 0;

   dynamic outlookApp = GetOutlookApplication();
   if (outlookApp == null)
   {
       MessageBox.Show("Outlook konnte nicht initialisiert werden.");
       return;
   }

   dynamic mailItem = outlookApp.CreateItem(olMailItem);
   mailItem.To = receipient;
   mailItem.Body = body;
   mailItem.Subject = subject;
   mailItem.Display();
   //mailItem.Send();
}

private dynamic GetOutlookApplication()
{
   // try to get an existing outlook instance
   try
   {
       dynamic outlookApp = System.Runtime.InteropServices.Marshal.GetActiveObject("Outlook.Application");
       return outlookApp;
   }
   catch (System.Runtime.InteropServices.COMException)
   {
   }

   // try to get an existing outlook instance
   try
   {
       dynamic outlookApp = System.Activator.CreateInstance(
          System.Type.GetTypeFromProgID("Outlook.Application"));
       return outlookApp;
   }
   catch
   {
       return null;
   }
}

Die Funktion GetOutlookApplication versucht zunächst eine laufende Outlook-Instanz zu ermitteln. Schlägt das fehl wird eine neue Instanz geöffnet. Dabei wird das Objekt über den Registry-Eintrag "Outlook.Application" referenziert. Dies ist ein Verweis auf die jeweils aktuelle Outlook-Installation.

Die ermittelte Outlook-Instanz wird dann verwendet, um ein neues MailItem zu erzeugen. Im Beispiel wird das Item nur angezeigt, damit der Kunde es noch bearbeiten kann. Durch die Verwendung von Late Binding mit dem dynamic Schlüsselwort ist zur Entwicklungszeit irrelevant, welche Version der Kunde installiert hat. Die Überprüfung erfolgt erst zur Laufzeit. Wird dann auf eine Eigenschaft oder Methode zugegriffen, die es bei dem real vorliegenden Objekt nicht gibt, so wird ein RuntimeException geworfen, die es zu behandeln gilt.