RESTful Web Service mit .NET und ExtJs: Service-Implementierung

Dieser Artikel ist Teil 3 einer mehrteiligen Serie, die sich mit dem Erstellen und Nutzen eines RESTful Web Service mit Hilfe von .NET und ExtJs beschäftigt.

Die Implementierung unserer IMessageService-Schnittstelle geht in der Klasse MessageService vor sich. Wir haben in dieser Schnittstelle Methoden für Erstellen, Löschen, Verändern und Abrufen, sowie für das Abrufen einer Liste von einzelnen Instanzen der ChatMessage-Klasse deklariert.

In MessageService bereiten wir nun den Programmcode vor, der festlegen wird, wie diese Methoden auszuführen sind.

Die Service-Implementierung beinhaltet den Code, der auf gespeicherte Daten zugreift und sie abruft, oder verändert. Um nicht unnötig vom Wesentlichen – dem Service selbst – abzulenken, beschränke ich mich dabei auf die Speicherung in einer XML-Datei.

Für die Zugriffsschicht auf das XML muss LinQ To Xml herhalten: so blasen wir den Code nicht unnötig weiter auf.

Die Informationen über die XML-Datei, in der die Daten gespeichert werden sollen, werden durch zwei Membervariablen gehalten: m_XmlFileName beinhaltet den Namen der Datei, m_MessageXml bereits das XDocument, mit dessen Hilfe wir den Zugriff auf die Daten realisieren. Beide werden im Konstruktor des Service gefüllt:

public class MessageService : IMessageService
{
  private XDocument m_MessageXml;
  private string m_XmlFilename;

  public MessageService()
  {
    // ich gehe davon aus, dass in der app.config ein 
    // Eintrag in den AppSettings vorhanden ist, der 
    // "messagefile" heisst und den Namen der Datei 
    // enthält - und davon, dass jene Datei auch im 
    // Hauptverzeichnis der Applikation ist
    m_XmlFilename = AppDomain.CurrentDomain.BaseDirectory + 
             ConfigurationManager.AppSettings["messagefile"];

    //Anlegen, falls nicht vorhanden
    if (!File.Exists(m_XmlFilename))
    {
      m_MessageXml = new XDocument(
        new XDeclaration("1.0", "utf-8", "yes"),
        new XElement("Messages")
      );
      m_MessageXml.Save(m_XmlFilename);
    }
    //Oder laden, falls vorhanden.
    else
      m_MessageXml = XDocument.Load(m_XmlFilename);
  }

Nachdem die Daten nun griffbereit in einem XDocument verfügbar sind, machen wir uns an das Auslesen einer Liste von ChatMessage-Objekten. Die Schnittstelle hat dafür die Methode ListMessages vorgesehen.

public List ListMessages()
{
  var result = m_MessageXml.Descendants("Message")
              .Select(p => new ChatMessage().Parse(p));
  return result.OrderByDescending(p => p.Time).ToList();
}

Dem kundigen Auge wird bereits die Methode Parse() aufgefallen sein. Keine Sorge, dazu kommen wir noch. Gut zu sehen dagegen ist, wieso ich mich für LinQ To Xml entschieden habe – sehr viel kürzer geht’s wirklich nicht mehr.

Der Zugriff auf einen einzelnen Datensatz, in der Schnittstelle durch die Methode GetMessage() deklariert, sieht ganz ähnlich aus. Allerdings gibt es einen kleinen Unterschied: wenn es den angeforderten Datensatz nicht gibt, geben wir nicht nur nichts zurück, sondern setzen den HTTP-Status der Antwort auf NotFound (Status Code 404). Sollte unser Client (also das Javascript im Browser) in der Lage sein, diese Status Codes auszuwerten, dann können wir sie auch benutzen, um den Grund eines Fehlschlags zu übermitteln.

public ChatMessage GetMessage(string messageId)
{
    XElement element = m_MessageList.Descendants("Message")
                      .FirstOrDefault(p => 
                        p.Attribute("id").Value == messageId
                      );
    if (element != null)
    {
        return new ChatMessage().Parse(element);
    }
    else
    {
        WebOperationContext.Current.OutgoingResponse.StatusCode = 
                                              HttpStatusCode.NotFound;
        return null;
    }
}

Parse() wurde schon wieder verwendet, ich weiß. Merkt’s euch, die Liste mit den unbekannten Methoden wird noch zwei Einträge länger.

Die nächsten zwei Methoden – erstellen und löschen einer Nachricht – können ohne große Erläuterung für sich selbst sprechen:

public ChatMessage CreateMessage(ChatMessage message)
{
    ChatMessage saveMessage = new ChatMessage(
                     message.Author, message.Text);
    //Nachricht im XML speichern
    saveMessage.Save(m_MessageList);	
    //und XML speichern
    m_MessageList.Save(m_XmlFilename);  

    WebOperationContext.Current.OutgoingResponse.StatusCode = 
                                       HttpStatusCode.Created;
    return saveMessage;
}

public ChatMessage DeleteMessage(string messageId)
{
    ChatMessage removeMessage = GetMessage(messageId);
    if (removeMessage != null)
    {
        removeMessage.Remove(m_MessageList);
    }
    m_MessageList.Save(m_XmlFilename);
    return removeMessage;
}

Da beim Löschen einer Nachricht auf die Methode GetMessage() zurückgegriffen wird, ist bereits für den Fall gesorgt, dass eine Nachricht gelöscht werden soll, die gar nicht existiert: in diesem Fall setzt GetMessage() bereits wie gehabt den Status Code auf 404.

Zu Kommentieren bleibt bloss, dass von der übermittelten Nachricht beim Erstellen lediglich die Inhalte benutzt werden: ID und die Zeit des Eintrags übernimmt offenbar unser ChatMessage-Objekt.
Die letzte verbleibende Methode dienst dem Aktualisieren von Einträgen:

public ChatMessage ModifyMessage(string messageId, 
                             ChatMessage messageToModify)
{
    if (GetMessage(messageId) != null)
    {
        messageToModify.MessageId = new Guid(messageId);
        messageToModify.Save(m_MessageList);
        m_MessageList.Save(m_XmlFilename);
        return messageToModify;
    }
    return null;
}

Auch das sollte einfach so verstanden werden – das einzig bemerkenswerte ist: die Nachricht wird anhand der übergebenen ID identifiziert, nicht etwa anhand der ID des übergebenen ChatMessage-Objekts: deshalb die Zuweisung der MessageId.

Na, die drei Methoden schon wieder vergessen, die neu waren? Wie versprochen, der überladene Konstruktur, die Save()-Methode, die Remove()-Methode, und die Parse()-Methode – alle drei zu ChatMessage gehörend. Im Wesentlichen erledigen sie das Mapping hin von einer Property von ChatMessage zu einem Knoten im XML.

public ChatMessage Parse(XElement xElement)
{
  MessageId = new Guid(xElement.Attribute("id").Value);
  Text = xElement.Element("text").Value;
  Time = DateTime.Parse(xElement.Element("time").Value);
  Author = xElement.Element("author").Value;
  return this;
}

public void Save(XDocument doc)
{
  XElement messageNode = doc.Descendants("Message")
          .FirstOrDefault(p => 
            p.Attribute("id").Value == MessageId.ToString());
  if (messageNode == null)
  {
    doc.Root.Add(
      new XElement("Message",
        new XAttribute("id", MessageId),
        new XElement("time", 
                       Time.ToString("dd.MM.yy HH:mm:ss")),
        new XElement("author", Author),
        new XElement("text", Text)
      )
    );
  }
  else
  {
    messageNode.Element("text").Value = Text;
    messageNode.Element("author").Value = Author;
    messageNode.Element("time").Value = 
                 Time.ToString("dd.MM.yy HH:mm:ss");
  }
  return this;
}

public ChatMessage Remove(XDocument doc)
{
  XElement messageNode = doc.Descendants("Message")
          .FirstOrDefault(p => 
            p.Attribute("id").Value == MessageId.ToString());
  if(messageNode != null) messageNode.Remove();
  return this;
}

Und damit kann der Service eigentlich bereits gestartet und durch einen Client angesprochen werden. Wie genau das funktioniert, wird im nächsten Teil geklärt.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: