code it

Martins Tech Blog

MVC-Views kompilieren

Im Standard werden MVC-Views beim ersten Zugriff kompiliert. Das hat einen großen Nachteil: Man erfährt erst beim Navigieren zu der View im Browser (oder bei der Ausführung der Coded UI Tests), dass ein Kompilierungsfehler vorliegt. Besonders bei größeren Änderungen an der Domäne wäre es aber schon sinnvoll, schon während des Continous Integration Builds zu wissen, wo noch Probleme vorliegen.

Die Einstellung ist ganz einfach. In der *.csproj-Datei des Web-Projektes gibt es eine Eigenschaft MvcBuildViews, die man lediglich auf true setzen muss.

<MvcBuildViews>true</MvcBuildViews>

Wenn man möchte, kann man diese Einstellung auch noch abhängig von der Build-Konfiguration setzen - also z.B. nur bei Release-Builds. Wer genau wissen möchte, wie man dazu vorgeht, dem empfehle ich die Blogposts von Malcolm Sheridan, der das allgemeine Vorgehen beschreibt und K. Scott Allan, der noch auf einige Besonderheiten (z.B. bei der Verwendung des Entity Frameworks) hinweist.

Access denied due to invalid credentials - aber nur manchmal?

Heute bin ich über ein sehr seltsames Phänomen gestolpert. Gesetzt ist eine ASP.NET MVC3 Anwendung. Im Debug auf dem lokalen IISExpress läuft diese auch wunderbar, und auch auf der Testumgebung (einem Windows Server 2008) läuft die Anwendung - zumindest so lange man mit localhost darauf zugreift. Alle Zugriffe über die IP-Adresse des Servers enden mit einem 401er-Fehler - und das sowohl von anderen Rechnern aus als auch vom Server selbst.

Und das Seltsame daran: Es betrifft auch nur diese eine Anwendung - alle anderen auf diesem Server laufenden Web-Anwendungen laufen problemlos und sind auch Remote erreichbar. Nach mehreren erfolglosen Einstellungsversuchen im IIS7 - Bindings, Berechtigungen und was man sonst alles falsch machen kann, IIS-Neustarts und sogar Serverneustarts ändert sich am Problem trotzdem nichts. Der Zugriff über localhost funktioniert, aber der Zugriff über die IP-Adresse eben nicht.

Letztendlich brachte ein Blogeintrag von John Tindell die rettende Lösung - und auch wenn mir wirklich unklar ist, WARUM das funktioniert - es funktioniert.

Lösung: Fügt man in der web.config im Bereich system.webServer den folgenden Eintrag hinzu und ruft die Seite dann nochmals auf, so funktioniert der Aufruf nun auch mit der IP-Adresse:

<httpErrors errorMode="Details" />

Seltsam daran: Dieser Eintrag hat eigentlich nur etwas damit zu tun, wie das Error-Handling für die Anwendung ist und wie demzufolge Fehlermeldungen ausgegeben werden. Detailed Errors anzuschalten kann natürlich nicht die Lösung sein, da man auf extern erreichbaren Servern natürlich keine Interna des Webservers und der Applikationsstruktur preisgeben möchte. Deswegen kann das noch nicht die finale Lösung sein.

Asynchrone Controller in MVC3

Die Zeiten in denen Anwender bereit waren, lange Wartezeiten in Kauf zu nehmen sind vorbei. Hat man eine Seite, die schwer zu beschaffende Informationen beinhaltet, so ist der Standardweg, dem Anwender zunächst schon einmal eine Seite auszuliefern, die dann mit Hilfe von asynchronen Requests (AJAX) die restlichen Seitenkomponenten nachlädt und im DOM ersetzt. Mit den aktuellen Frameworks und Bibliotheken ist das alles recht unproblematisch und schnell implementiert. Und so ist man auch in den meisten Fällen mit der Kombination aus synchronen Controller-Actions, Partial Views und AJAX recht gut bedient.

In bestimmten Fällen kann es aber auch sinnvoll sein, serverseitig asynchrone Prozesse zu verwenden. Auch wenn alles recht einfach implementiert ist (wie ich gleich zeigen werde), so ist im Vorab die Sinnhaftigkeit zu hinterfragen. Die Umstellung auf asynchrone Aktionen bringt nur dann etwas, wenn beispielsweise unabhängig voneinander mehrere Backend-Services abgefragt werden und die Backend-Systeme auch in der Lage sind, diese Anfragen parallel zu beantworten. In diesem Fall würde im Vergleich zur sequentiellen Abarbeitung in der sich die Ausführungszeit aus der Summe der Subrequests zusammensetzt die parallele Abarbeitung nur etwas länger als der längste Request dauern.

Das Vorgehen ist nicht kompliziert: Zunächst ändert man die Basisklasse des Controllers. Statt von Controller zu erben, verwendet man nun AsyncController.

public class HomeController : AsyncController
{
     // todo: add some views
}

Auch mit dieser Basisklasse sind nach wie vor synchrone Anfragen möglich - es wird ledigleich zusätzlich die Möglichkeit gegeben, serverseitig asynchrone zu arbeiten.

Der nächste Schritt betrifft die Action. Synchrone Controller-Actions bestehen aus einer Methode, die normalerweise den Namen der View haben und als Rückgabeparameter ein ActionResult haben. Das folgende Beispiel würde die View Index ausliefern.

public ActionResult Index()
{
    // do something
    return View();
}

Im Fall der asynchronen Controller-Action muss diese Methode gesplittet werden: Die erste Methode (im Beispiel IndexAsync) nimmt den Request entgegen und startet die asynchronen Hintergrundprozesse. Diese Methode hat keinen Rückgabewert. Dieser ist erst in der zweiten Methode vorhanden (im Beispiel IndexCompleted). Hier erfolgt die Verarbeitung der Rückgabewerte der Hintergrund-Requests und die Auslieferung der View. Wie häufig im ASP.NET MVC, so ist die konventionsgemäße Benennung der Methoden wichtig, damit die Magie funktioniert.

Wichtig ist, dass man nicht vergessen darf, dass man die Abarbeitung in den Thread-Pool übergibt und es dort ggf. einige Kontext-Objekte nicht mehr gibt und dass man die Kontrolle in einem statischen Kontext zurück bekommt. Zur Unterstützung der Abarbeitung steht im AsyncController ein AsyncManager zur Verfügung. Mit dessen Eigenschaft OutstandingOperations definiert man, wie viele offene Hintergrundprozesse es gibt. Wann immer ein Subrequest ein Ergebnis zurückliefert, schreibt man das Ergebnis in die Parameters-Auflistung des AsyncManagers zurück und verringert die OutstandingOperations.

Mein komplettes Beispiel sieht nun wie folgt aus:

public class HomeController : AsyncController
{

    public void IndexAsync()
    {
        AsyncManager.OutstandingOperations.Increment();
        var webRequest = WebRequest.Create(
                "http://api.twitter.com/1/statuses/user_timeline.xml"
                + "?screen_name=martin_hey");


        Observable
        .FromAsyncPattern<WebResponse>(webRequest.BeginGetResponse, 
            webRequest.EndGetResponse)()
        .Subscribe(response =>
                        {
                            var streamReader = new StreamReader(
                                response.GetResponseStream());
                            var responseText = streamReader.ReadToEnd();
                            AsyncManager.Parameters["TweetXml"] = responseText;
                            AsyncManager.OutstandingOperations.Decrement();
                        });
    }



    public ActionResult IndexCompleted(string tweetXml)
    {
        var model = new TweetViewModel
                        {
                            TimelineXml = tweetXml
                        };
        return View(model);
    }

}

In IndexAsync erfolgt ein Webrequest auf ein Drittsystem - im echten Leben wären hier mehrere Requests. Im der Callback-Action dieses Requests schreibe ich das erhaltene Ergebnis in die Parameter des AsyncManagers. Man könnte hier auch komplette Objekte oder Objektstrukturen ablegen, ich beschränke mich aber der Einfachheit halber auf den XML-String. Ist OutstandingOperations wieder auf 0, so wird IndexCompleted automatisch aufgerufen und die Parameter im AsyncManager auf die Parameter der Methode gemappt. Hier erfolgt dann die restliche Verarbeitung - in der Regel der Aufbau des Models zur Darstellung der View und die Rückgabe der View.

Einen kleinen Nachteil hat diese Umstellung noch: Kann man in der synchronen Verarbeitung problemlos mit Hilfe von Profiler-Werkzeugen (z.B. MVC Mini Profiler) eingreifen und beispielsweise die Laufzeit der Subrequests anzeigen, so wird das im asynchronen Kontext schon komplizierter bis unmöglich.

Englische Version Englische Version

Einladung zum Treffen der .NET Usergroup Dresden am 14.07.2011

Das kommende Treffen der .NET Usergroup Dresden wird am 14.07.2011 bei Saxonia Systems stattfinden. Dabei geht es hauptsächlich um die folgenden Themen:

  1. MEF (Mario Kretschmer)
    Das Microsoft Extensibility Framework ist eine neue Bibliothek im .NET Framwork 4, die es recht leicht macht, erweiterbare Anwendungen zu schreiben. Mit Hilfe weniger Attribute kann man so die Komplexität solcher Anwendungen stark verringern und kann dynamisch Module nachladen (AddIn-Konzept).
  2. BDD mit SpecFlow (Hendrik Lösch)
    SpecFlow versucht, die Lücke zwischen Entwicklern und  Domain-Experten zu schließen. Dazu bietet es einen Editor, in dem man seine Anforderungen an die Domain recht normalsprachlich (im Gherkin-Format) in feature-Dateien herunterschreiben kann. Aus diesen werden dann Testschritte generiert, die in Unittests automatisiert getestet werden können.
Im Anschluss daran gibt es wieder Gelegenheit, gemeinsam zu netzwerken. Nähere Informationen und einen Link zur Anmeldeliste finden sich auf der Webseite der .NET Usergroup Dresden.

StyleCop Upgrade Dialog verhindern

StyleCop ist ein wirklich gutes Tool zur Unterstützung der Durchsetzung von Coding Guidelines. Mit Version 4.5 ist es auch nicht mehr notwendig zusätzlicher AddOns zu installieren, damit es sich automatisch in Resharper integriert - das übernimmt alles die Installationsroutine von StyleCop.
 
Seit der Installation beglückt mich StyleCop nun aber jeden Morgen mit der freudigen Nachricht, dass es wieder eine neue Version zum Herunterladen gibt, die ich doch bitte installieren soll. Vielleicht waren die Releasezyklen von StyleCop auch bisher so kurz und ich habe es nur nicht gemerkt, weil es dieses Check-Feature bisher nicht gab. Auf jeden Fall kann dieser Dialog ganz schön nervig sein, da er wenn man ihn denn mit Nein beantwortet, bei jedem Start von Visual Studio wieder kommt und es keine offensichtliche Möglichkeit gibt, diesen zu unterbinden.
 
Für das Problem gibt es zwei Lösungen:
Die erste Lösung setzt voraus, dass man Resharper installiert. In diesem Fall gibt es in den Optionen von Resharper unter dem Punkt Tools/StyleCop die Möglichkeit, einzustellen, ob und wann StyleCop nach Upgrades suchen soll. Im Standard ist hier "Every Time Visual Studio starts" aktiviert.
Die zweite Lösung ist das manuelle Setzen der Einstellungen in der Registry. Die Einstellungen finden sich dort unter HKEY_CURRENT_USER\Software\CodePlex\StyleCop.
 
Wenn man der Diskussion auf Codeplex und der dort genannten Releaseplanung Glauben schenken darf, wird es in Version 5.0 von Stylecop auch eine Möglichkeit geben, diese Einstellung direkt über einen von StyleCop mitgelieferten Konfigurationsdialog zu editieren (für all die Anwender, die kein Resharper haben).

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

Einladung zum Treffen der .NET Usergroup Dresden am 16.06.2011

Das Juni-Treffen der .NET Usergroup Dresden wird am 16.06.2011 stattfinden. Die Veranstaltung beginnt um 18:00 Uhr in den Räumen der Saxonia Systems AG.
 
Thematisch wird es dabei um zwei Themenblöcke gehen, die beide Mathias Raake bestreiten wird:
 
Zum einen werfen wir einen Blick auf PostSharp. Die Implementierung von INotifyPropertyChanged, Logging-Code, Exception-Handling oder auch Caching sind Funktionalitäten, die man in vielen Teilen der Anwendung immer wieder implementieren muss. Dabei haben diese Funktionen mit der eigentlichen Logik eurer Anwendung wenig zu tun. Die Aspektorientierte Programmierung (AOP) ermöglicht es euch, euren Code wieder auf das Wesentliche zu beschränken und "Infrastrukturfunktionen" in sogenannte Aspekte auszulagern. Euer Code wird kürzer, lesbarer und somit wartbarer, und die Aspekte können einfach wiederverwendet werden. Der Vortrag zeigt anhand von PostSharp und Microsoft Unity, wie ihr AOP in Ihren Anwendungen nutzen könnt.
 
Im Anschluss daran schauen wir uns gemeinsam NLocalize an.Wer bisher seine WPF-Anwendungen mit LocBaml lokalisiert hat, dem sei ein Blick auf dieses Tool empfohlen. Neovelop NLocalize unterstützt euch bei der Lokalisierung von WPF- und Silverlight Anwendungen. Als Visual Studio 2010 Plugin ermöglicht NLocalize die Lokalisierung direkt in Visual Studio. Für Übersetzer gibt es einen eigenen Client, der sich durch eine hohe Benutzerfreundlichkeit auszeichnet.
 
Wie immer, so wird es auch dieses Mal wieder im Anschluss die Möglichkeit geben, bei einem Bierchen zu netzwerken.

Falscher Redirect auf SharePoint-Formulare

SharePoint-Listen bringen auch immer ihre eigenen Formulare mit, mit denen Listenelemente angeschaut (DispForm.aspx), editiert (EditForm.aspx) oder erzeugt (NewForm.aspx) werden können. Im Standardfall beinhalten diese Formulare ein ListFormWebPart, mit dessen Eigenschaft FormType definiert wird, welche Funktion das WebPart bereitstellen soll. Diese Formulare können auch an eigene Bedürfnisse angepasst werden, um weitere Funktionalitäten an dieser Stelle bereitzustellen.

Editiert man diese Formulare zu intensiv, so kann es sein, dass SharePoint verweigert, auf die Formulare zu verweisen. Das merkt man daran, dass im Kontextmenü die Menüpunkte "Element anzeigen" oder "Element bearbeiten" auf die Hauptseite der SiteCollection verweisen und nicht mehr die Listenformulare aufgerufen werden. Gut ist beraten, wer dann noch die Originalversion der Listenformulare vorliegen hat (oder zumindest eine Version, die noch funktioniert hat). Hat man dies nicht, so hat man in der Regel extrem viel Spaß dabei, die Seite wiederherzustellen, denn SharePoint verwendet intern jede Menge Guids, die auch hier dann benötigt werden.

Eine kleine Hilfestellung, was man bei der Wiederherstellung der Standardformulare beachten muss, gibt Ben in seinem Blog. Am wichtigsten hierbei: Guid der Liste, Guid des WebParts und FormType des ListFormWebParts.

Id des Listenelements im EditForm.aspx anzeigen

Im Standard werden einige Felder nicht in EditForm.aspx oder DispForm.aspx angezeigt. Eines dieser Felder ist die ID des Listeneintrags. Auf eine SQL-Tabelle projiziert, handelt es sich dabei um den internen Identifier, der den Eintrag eindeutig identifiziert und automatisch inkrementiert wird.
 
Da diese Spalte von SharePoint automatisch verwaltet wird, ist es in vielen Anwendungsfällen auch durchaus gerechtfertigt, diese nicht anzuzeigen, um so auch nicht den Eindruck zu erwecken, dieses Feld könnte editiert werden. Nun gibt es aber auch Anwendungsfälle, da möchte man, dass ein solcher Identifier auch angezeigt wird, einfach weil man so schon eine laufende Nummer hat. Beispiel dafür ist ein Bugtracking oder auch ein Support-System, wie es das Help-Center-Template schon mitbringt.
 
Für das Problem, die ID des Listeneintrags anzuzeigen gibt es im Netz mehrere Lösungen. Meist läuft die Lösung darauf hinaus, mit JavaScript oder Codebehind den QueryString-Parameter ID aus der Url auszulesen. Eine weitere Möglichkeit, und wenn man die bestehenden Seiten mal etwas genauer betrachtet vermutlich die von den SharePoint-Entwicklern vorgesehene Lösung ist es, die Klasse ListItemProperty zu verwenden. Damit kann man, wie der Name schon vermuten lässt, auf die Eigenschaften des zugrundeliegenden ListItems zugreifen.
 
<SharePoint:ListItemProperty Property="ID" id="ID" runat="server"/>
 
Dieses Control findet man im SharePoint Designer in der Toolbox.
 
Noch ein kleiner Tipp am Rande: Ähnlich wie ListItemProperty gibt es auch noch ListProperty, mit dem man Eigenschaften der Liste anzeigen kann.