code it

Martins Tech Blog

Treffen der .NET Usergroup Dresden

Im gestrigen Treffen der .NET Usergroup ging es um das Thema Silverlight. In einem Workshop haben wir gemeinsam eine kleine mehrschichtige datengebundene Applikation zusammengebaut. Mit dabei waren Silverlight-Newbees, aber auch Professionals und Grafiker. So war es für alle recht interessant, da jeder seine Erfahrungen mit einbringen konnte und auch auf den ein oder anderen Stolperstein aufmerksam gemacht wurde oder Tricks verraten wurden.

Beim anschließenden Stammtisch-Treffen im Newtown kam dann die soziale Komponente noch etwas mehr zum tragen und bei dem ein oder anderen Bier kam es dann zu tiefgreifenden philosophischen Erkenntnissen.

Eine Downloadmöglichkeit für den Beispielcode wird sicher in den nächsten Tagen noch im Blog der Usergroup bereitgestellt.

MSBuild-Messages im Output-Window anzeigen lassen

Das Output-Window von Visual Studio kann mittels einer Option dazu gebracht werden, detaillierter anzuzeigen, was bei der Ausführung der internen aber auch von angepassten MSBuild-Skripten eigentlich gemacht wird. Die Einstellung dafür findet man unter Tools -> Options -> Projects and Solutions -> Build and Run.

Die "MSBuild project build output verbosity" bestimmt, wie geschwätzig sich MSBuild verhalten soll.

.NET OpenSpace 2009 Leipzig - ein Rückblick

Am 17./18.10. fand in Leipzig der .NET OpenSpace statt. Über 150 Teilnehmer waren angemeldet und auch fast so viele sind erschienen, um sich mit anderen Gleichgesinnten auszutauschen. Dabei ging es zum einen um eher fachlich orientierte Themen wie TDD, BDD, AOP, T4, ASP.NET MVC, Entity Framework, Deployment, Architektur von Anwendungen, Neuigkeiten für das Team System, Anforderungsanalyse, Refactoring, CCD, Silverlight in Business Anwendungen. Aber es ging auch um Engagement in der Community, Weiterbildung, Attraktivität des Berufes Softwareentwickler. Für alle Technik-Affinen (und welcher Softwareentwickler ist das nicht): Live-Eindrücke aus den verschiedenen Sessions konnte jeder an der Twitter-Wall verfolgen, an der alle Tweets mit dem Hashtag #netos2009 angezeigt wurden. Innerhalb der Sessions aber auch während der Kaffeepausen dazwischen konnte man mit anderen ins Gespräch kommen und neue Kontakte knüpfen und alte wiederaufleben lassen. Zusammengefasst: Es war eine gelungene Veranstaltung. Vielen Dank an die Organisatoren, die Sponsoren und Teilnehmer. Ich freue mich schon auf eine Fortsetzung.

Wie neu ist "!Neu"?

Neben neuen Listeneinträgen erscheint im SharePoint immer das “!New”-Icon. Aber wie lange erscheint dieses Icon eigentlich? Wie immer ist die Antwort schnell gefunden, wenn man weiß, wo man suchen muss: Im Standard erscheint das Icon bis zum Ende des folgenden Kalendertages.

Die Einstellung dafür kann man mit Hilfe des Kommandozeilentools stsadm.exe (zu finden unter %COMMONPROGRAMFILES%\Microsoft Shared\web server extensions\12\BIN) auslesen und auch setzen. Der Vorteil dieser Möglichkeit liegt auf der Hand: Elemente können auch für einen längeren Zeitraum als neu erscheinen oder die Markierung kann ganz entfernt werden.

Um den aktuell gesetzten Wert auszulesen, verwendet man folgenden Befehl:

stsadm -o getproperty -pn days-to-show-new-icon -url http://localhost

Im Beispiel ist der Rückgabewert 2. das entspricht dem Standardverhalten. Das Icon wird also 2 Kalendertage lang angezeigt (bis zum Ende des Folgetages).

Mit Hilfe der Operation setproperty kann dieser Wert geändert werden. Beispielsweise unterdrückt der Wert 0 das “!New”-Icon, während der Wert 7 das Icon 7 Kalendertage lang erscheinen lässt.

stsadm -o setproperty -pn days-to-show-new-icon -pv 7 -url http://localhost

Diese Einstellung greift direkt – es ist kein Neustart (iisreset) notwendig.

Mit LINQ To Objects fehlende Elemente finden

Gelegentlich möchte man nicht nur wissen, welche Werte vorliegen, sondern auch welche Werte fehlen. Ein Beispiel dafür ist folgendes Szenario: Man erwartet Werte für jede Kalenderwoche des Jahres und erhält aber nur 49 Werte. Statt mit einer Schleife durch alle möglichen Werte zu iterieren kann man dazu auch LINQ bemühen:

public static class ExtensionMethods
{
    /// <summary>
    /// Gets the missing numbers in a list of integers.
    /// </summary>
    /// <param name="list">A list of integers</param>
    /// <param name="first">Minimum value</param>
    /// <param name="last">Maximum value</param>
    /// <returns>Missing integers</returns>
    public static IEnumerable<int> GetMissing(this IEnumerable<int> list, int first, int last)
    {
        // range that contains all numbers in the interval
        var range = Enumerable.Range(first, last - first + 1);

        // getting the difference
        var missingNumbers = range.Except(list);

        return missingNumbers;
    }

    /// <summary>
    /// Gets the missing numbers in a list of integers.
    /// </summary>
    /// <param name="list">A list of integers</param>
    /// <returns>Missing integers</returns>
    public static IEnumerable<int> GetMissing(this IEnumerable<int> list)
    {
        if (list.Count() < 2)
            return new int[0];
            
        // min value
        int first = list.Min();
        // max value
        int last = list.Max();

        // range that contains all numbers in the interval
        var range = Enumerable.Range(first, last - first + 1);

        // getting the difference
        var missingNumbers = range.Except(list);

        return missingNumbers;
    }
}

Das Prinzip ist recht simpel: Zunächst wird ermittelt, wie der erwartete Wertebereich ist und mittels der Extension-Method Range() eine Variable mit allen erwarteten Werten gefüllt. Im Anschluss daran ermittelt die Extension-Method Except() alle fehlenden Werte.

Zugriff auf Apache ActiveMQ mit Hilfe von Spring.NET

Spring.NET bietet out-of-the-box die Möglichkeit, um ohne großen Aufwand auf ActiveMQ-Queues zuzugreifen. Die Einrichtung auf einer Entwicklermaschine ist ebenfalls recht einfach und soll im Folgenden kurz angerissen werden. Dieses System beinhaltet sowohl Server- als auch Clientkomponenten. Im produktiven Einsatz ist das dann logischerweise dem jeweils richtigen System zuzuordnen. Die Basics dazu hab ich dem aus meiner Sicht sehr guten Tutorial von Mark Bloodworth entnommen.

Installation der aktuellen JRE
Voraussetzung für ActiveMQ ist das aktuelle Java Runtime Environment. Dieses kann bei Sun heruntergeladen werden und im Anschluss installiert werden.

Installation von ActiveMQ
Die aktuelle Version von ActiveMQ kann von Apache bezogen werden. Nach Entpacken des Archives öffnet man die Kommandozeile und wechselt in den Ordner in den das Paket entpackt wurde. Dort gibt man bin\activemq ein. Dies startet ActiveMQ. Es sollte folgendes Fenster erscheinen:

ActiveMQ läuft damit. Mit der Eingabe von <Strg>+<C> kann es beendet werden. Zur Prüfung, ob ActiveMQ läuft, kann in einem weiten Konsolenfenster mit Hilfe des folgenden Aufrufs überprüft werden:

netstat -an|find "61616"

Installation von Spring.NET
Spring.NET enthält eine Messaging API (NMS). Es kann auf der Spring.NET-Website heruntergeladen und dann installiert werden. Die folgenden Ausführungen gehen von einer Installation in das Verzeichnis "C:\Program Files\Spring.NET 1.2.0" aus.

Implementierung eines Request-Response-Szenarios mit ActiveMQ und Spring.NET
Nun da alle Komponenten installiert sind, geht es an die Implementierung. Für die Funktionsfähigkeit sind Referenzen auf folgende Assemblies notwendig (in Klammern der Deployment-Ordner nach der Installation):

  • Apache.NMS (C:\Program Files\Spring.NET 1.2.0\lib\Net\2.0\Apache.NMS.dll)
  • Apache.NMS.ActiveMQ (C:\Program Files\Spring.NET 1.2.0\lib\Net\2.0\Apache.NMS.ActiveMQ.dll)
  • Spring.Core (C:\Program Files\Spring.NET 1.2.0\bin\net\2.0\debug\Spring.Core.dll)
  • Spring.Messaging.Nms (C:\Program Files\Spring.NET 1.2.0\bin\net\2.0\debug\Spring.Messaging.Nms.dll) 

Für den Aufbau eines Request-Response-Szenarios sind 2 Komponenten erforderlich – eine Server-Komponente und eine Client-Komponente. Anfragen werden an den Server verschickt und dieser antwortet. Beide sind im folgenden Beispiel als Konsolen-Applikation implementiert.

Client-Komponente

Program.cs

using System;

using Apache.NMS;
using Apache.NMS.ActiveMQ;
using Spring.Messaging.Nms.Support.Destinations;

namespace ActiveMqRequestResponseConsole
{
    class Program
    {
        private const string URI = "tcp://localhost:61616";
        private const string DESTINATION = "test.queue";

        static void Main(string[] args)
        {
            ConnectionFactory connectionFactory = new ConnectionFactory(URI);
            try
            {
                using (IConnection connection = connectionFactory.CreateConnection())
                {
                    using (ISession session = connection.CreateSession())
                    {
                        ITemporaryQueue queue = session.CreateTemporaryQueue();
                        using (IMessageConsumer consumer = session.CreateConsumer(queue))
                        {
                            string text = "A message for you.";
                            ITextMessage message = session.CreateTextMessage(text);
                            message.NMSReplyTo = queue;
                            string correlationId = Guid.NewGuid().ToString();
                            message.NMSCorrelationID = correlationId;
                            using (IMessageProducer producer = session.CreateProducer())
                            {
                                NmsDestinationAccessor destinationResolver = new NmsDestinationAccessor();
                                IDestination destination = destinationResolver.ResolveDestinationName(session, DESTINATION);
                                producer.Send(destination, message);
                            }
                            IMessage response = consumer.Receive(TimeSpan.FromSeconds(60));
                            ITextMessage textMessage = response as ITextMessage;
                            Console.WriteLine(textMessage.Text);
                        }

                    }
                }

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }
    }
}

Die Konstanten im Beispiel definieren das Ziel der Anfrage
URI = ActiveMQ-Server und Port
DESTINATION = Queue-Name

Im Beispiel wird der Text "A message for you" in die Queue mit dem Namen "test.queue" geschrieben.

Server-Komponente

Program.cs

using System;

using Apache.NMS.ActiveMQ;
using Spring.Messaging.Nms.Listener;

namespace ActiveMqServer
{
    class Server
    {
        private const string URI = "tcp://localhost:61616";
        private const string DESTINATION = "test.queue";

        static void Main(string[] args)
        {
            try
            {
                ConnectionFactory connectionFactory = new ConnectionFactory(URI);
                using (SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer())
                {
                    listenerContainer.ConnectionFactory = connectionFactory;
                    listenerContainer.DestinationName = DESTINATION;
                    listenerContainer.MessageListener = new ActiveMqServer.Listener(listenerContainer);
                    listenerContainer.AfterPropertiesSet();
                    Console.WriteLine("Listener started.");
                    Console.WriteLine("Press any key to exit.");
                    Console.ReadLine();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

Listener.cs

using System;

using Apache.NMS;
using Spring.Messaging.Nms.Listener;

namespace ActiveMqServer
{
    class Listener : ISessionAwareMessageListener
    {
        private readonly SimpleMessageListenerContainer container;

        public Listener(SimpleMessageListenerContainer container)
        {
            this.container = container;
            Console.WriteLine("Listener created.");
        }

        #region ISessionAwareMessageListener Members
        public void OnMessage(IMessage message, ISession session)
        {
            Console.WriteLine("Message Received.");
            ITextMessage textMessage = message as ITextMessage;
            string incomingText = textMessage.Text;
            Console.WriteLine("Message: {0}", incomingText);
            string outgoingText = string.Format("Thanks for sending the following message: {0}", incomingText);

            IDestination destination = message.NMSReplyTo;
            if (destination != null)
            {
                ITextMessage response = session.CreateTextMessage(outgoingText);
                response.NMSCorrelationID = message.NMSCorrelationID;
                using (IMessageProducer producer = session.CreateProducer(destination))
                {
                    producer.Send(response);
                }
            }
        }
        #endregion
    }
}

Im Beispiel wird ein Listener registriert. Empfängt dieser eine Text-Nachricht, so antwortet er dem Sender mit dem Text "Thanks for sending the following message <Ursprungsnachricht>".

Auch hier wird über den Queuenamen und die ActiveMQ-Server-Uri festgelegt, welche Queue überwacht werden soll und in welche Queue die Antwort geschrieben werden soll. Durch die Zuordnung der Correlation-Ids können beide Systeme abgleichen zu welcher Anfrage die Antwort gehörte.

Treffen der .NET UserGroup Dresden

Das nächste Treffen der .NET Usergroup Dresden findet am 28.10. im Gebäude der T-Systems MMS statt. Beginn ist wie immer 18:00 Uhr. Thema an diesem Abend ist Silverlight 3 und Peter Fleischer (MVP für Visual Basic und Datenbanken) hat zu diesem Thema einen Workshop vorbereitet in dem gemeinsam eine mehrschichtige Anwendung aufgebaut werden soll.

Aus diesem Grund wäre es auch gut, wenn jeder Teilnehmer sein Notebook mitbringt auf dem schon folgende Programme und Tools installiert sind:
  • Visual Studio 2008 mit SP1
  • Silverlight 3 Tools und ggf. Toolkit
  • SQL Server 2005 oder 2008 (Express Edition ist ausreichend) + Management-Studio
Weitere Informationen zum Termin und einen Link zur Anmeldeliste findet man auf der Seite der .NET Usergroup.

DefaultView einer SPList anpassen

Möchte man die DefaultView einer SPList über das Objektmodell aktualisieren, so trifft man auf ein etwas seltsames Verhalten. Folgender Codeausschnitt bringt nicht den gewünschten Erfolg:

private static void UpdateDefaultView()
{
    using (SPSite site = new SPSite(SITECOLLECTIONURL))
    {
        using (SPWeb web = site.RootWeb)
        {
            SPList list = web.Lists["Employees"];
            list.DefaultView.ViewFields.Add("Address");
            list.DefaultView.Update();
        }
    }
}

Die Ansicht ist hinterher noch genauso wie vorher. Der Grund für dieses Verhalten wird klar, wenn man einen kurzen Blick in die MSDN wirft: Die Methode SPList.DefaultView gibt bei jedem Aufruf eine neue Instanz zurück. Durch dieses Verhalten wird die Veränderung verworfen.

Nun da die Ursache klar ist, ist auch geklärt, wie dieses Verhalten umgangen werden kann: Man speichert die Referenz auf die DefaultView in einer Variablen und aktualisiert diese:

private static void UpdateDefaultView()
{
    using (SPSite site = new SPSite(SITECOLLECTIONURL))
    {
        using (SPWeb web = site.RootWeb)
        {
            SPList list = web.Lists["Employees"];
            SPView view = list.DefaultView;
            view.ViewFields.Add("Address");
            view.Update();
        }
    }
}