Freigeben über


Häufiges Senden von Echtzeitnachrichten mit SignalR 1.x

von Patrick Fletcher

Warnung

Diese Dokumentation gilt nicht für die neueste Version von SignalR. Sehen Sie sich ASP.NET Core SignalR an.

In diesem Tutorial wird gezeigt, wie Sie eine Webanwendung erstellen, die ASP.NET SignalR verwendet, um hochfrequente Messagingfunktionen bereitzustellen. Hochfrequenzmessaging bedeutet in diesem Fall Updates, die mit einer festen Rate gesendet werden; bei dieser Anwendung bis zu 10 Nachrichten pro Sekunde.

Die Anwendung, die Sie in diesem Tutorial erstellen, zeigt eine Form an, die Benutzer ziehen können. Die Position des Shapes in allen anderen verbundenen Browsern wird dann aktualisiert, um der Position des gezogenen Shapes mit zeitgesteuerten Updates zu entsprechen.

Die in diesem Tutorial vorgestellten Konzepte enthalten Anwendungen für Echtzeitspiele und andere Simulationsanwendungen.

Kommentare zum Tutorial sind willkommen. Wenn Sie Fragen haben, die nicht direkt mit dem Tutorial zusammenhängen, können Sie diese im ASP.NET SignalR-Forum oder StackOverflow.com posten.

Überblick

In diesem Tutorial wird veranschaulicht, wie Sie eine Anwendung erstellen, die den Status eines Objekts in Echtzeit mit anderen Browsern teilt. Die Anwendung, die wir erstellen, heißt MoveShape. Auf der MoveShape-Seite wird ein HTML-Div-Element angezeigt, das der Benutzer ziehen kann. wenn der Benutzer das Div zieht, wird seine neue Position an den Server gesendet, der dann alle anderen verbundenen Clients anweisen, die Position des Shapes so zu aktualisieren, dass sie übereinstimmen soll.

Screenshot der MoveShape-Anwendungsseite

Die in diesem Tutorial erstellte Anwendung basiert auf einer Demo von Damian Edwards. Ein Video mit dieser Demo finden Sie hier.

Das Tutorial zeigt zunächst, wie SignalR-Nachrichten von jedem Ereignis gesendet werden, das ausgelöst wird, wenn die Form gezogen wird. Jeder verbundene Client aktualisiert dann die Position der lokalen Version des Shapes jedes Mal, wenn eine Nachricht empfangen wird.

Obwohl die Anwendung diese Methode verwendet, ist dies kein empfohlenes Programmiermodell, da es keine Obergrenze für die Anzahl der gesendeten Nachrichten gibt, sodass die Clients und der Server mit Nachrichten überfordert werden und die Leistung beeinträchtigt würde. Die auf dem Client angezeigte Animation wäre auch nicht miteinander zusammenhängend, da die Form von jeder Methode sofort verschoben würde, anstatt sich reibungslos an jede neue Position zu bewegen. In den späteren Abschnitten des Tutorials wird veranschaulicht, wie Sie eine Timerfunktion erstellen, die die maximale Geschwindigkeit einschränkt, mit der Nachrichten entweder vom Client oder Server gesendet werden, und wie das Shape reibungslos zwischen Speicherorten verschoben werden kann. Die endgültige Version der Anwendung, die in diesem Tutorial erstellt wurde, kann aus dem Codekatalog heruntergeladen werden.

Dieses Tutorial enthält die folgenden Abschnitte:

Voraussetzungen

Für dieses Tutorial ist Visual Studio 2012 oder Visual Studio 2010 erforderlich. Wenn Visual Studio 2010 verwendet wird, verwendet das Projekt .NET Framework 4 statt .NET Framework 4.5.

Wenn Sie Visual Studio 2012 verwenden, wird empfohlen, das update ASP.NET and Web Tools 2012.2 zu installieren. Dieses Update enthält neue Features wie Verbesserungen bei der Veröffentlichung, neue Funktionen und neue Vorlagen.

Wenn Sie Über Visual Studio 2010 verfügen, stellen Sie sicher, dass NuGet installiert ist.

Erstellen des Projekts

In diesem Abschnitt erstellen wir das Projekt in Visual Studio.

  1. Klicken Sie im Menü Datei auf Neues Projekt.

  2. Erweitern Sie im Dialogfeld Neues Projekt unter Vorlagenden Eintrag C#, und wählen Sie Web aus.

  3. Wählen Sie die Vorlage ASP.NET Leere Webanwendung aus, nennen Sie das Projekt MoveShapeDemo, und klicken Sie auf OK.

    Erstellen des neuen Projekts

Hinzufügen der NuGet-Pakete SignalR und JQuery.UI

Sie können signalR-Funktionalität zu einem Projekt hinzufügen, indem Sie ein NuGet-Paket installieren. In diesem Tutorial wird auch das JQuery.UI-Paket verwendet, damit die Form gezogen und animiert werden kann.

  1. Klicken Sie auf Extras | NuGet-Paket-Manager | Paket-Manager-Konsole.

  2. Geben Sie den folgenden Befehl im Paket-Manager ein.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    Das SignalR-Paket installiert eine Reihe anderer NuGet-Pakete als Abhängigkeiten. Nach Abschluss der Installation verfügen Sie über alle Server- und Clientkomponenten, die für die Verwendung von SignalR in einer ASP.NET-Anwendung erforderlich sind.

  3. Geben Sie den folgenden Befehl in die Paket-Manager-Konsole ein, um die Pakete JQuery und JQuery.UI zu installieren.

    Install-Package jQuery.ui.combined
    

Erstellen der Basisanwendung

In diesem Abschnitt erstellen wir eine Browseranwendung, die den Speicherort des Shapes während jedes Mausbewegungsereignisses an den Server sendet. Der Server sendet diese Informationen dann an alle anderen verbundenen Clients, während sie empfangen werden. Wir erweitern diese Anwendung in späteren Abschnitten.

  1. Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie Hinzufügen, Klasse... aus. Nennen Sie die Klasse MoveShapeHub, und klicken Sie auf Hinzufügen.

  2. Ersetzen Sie den Code in der neuen MoveShapeHub-Klasse durch den folgenden Code.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class MoveShapeHub : Hub
        {
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
    }
    

    Die MoveShapeHub obige Klasse ist eine Implementierung eines SignalR-Hubs. Wie im Tutorial Erste Schritte mit SignalR verfügt der Hub über eine Methode, die von den Clients direkt aufgerufen wird. In diesem Fall sendet der Client ein Objekt mit den neuen X- und Y-Koordinaten des Shapes an den Server, das dann an alle anderen verbundenen Clients gesendet wird. SignalR serialisiert dieses Objekt automatisch mithilfe von JSON.

    Das Objekt, das an den Client (ShapeModel) gesendet wird, enthält Elemente, um die Position des Shapes zu speichern. Die Version des Objekts auf dem Server enthält auch einen Member, um nachzuverfolgen, welche Clientdaten gespeichert werden, sodass für einen bestimmten Client keine eigenen Daten gesendet werden. Dieses Element verwendet das JsonIgnore -Attribut, um zu verhindert, dass es serialisiert und an den Client gesendet wird.

  3. Als Nächstes richten wir den Hub ein, wenn die Anwendung gestartet wird. Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und klicken Sie dann auf Hinzufügen | Globale Anwendungsklasse. Übernehmen Sie den Standardnamen Global, und klicken Sie auf OK.

    Hinzufügen einer globalen Anwendungsklasse

  4. Fügen Sie die folgende using Anweisung nach den bereitgestellten using-Anweisungen in der Global.asax.cs-Klasse hinzu.

    using System.Web.Routing;
    
  5. Fügen Sie die folgende Codezeile in der Application_Start Methode der Global-Klasse hinzu, um die Standardroute für SignalR zu registrieren.

    RouteTable.Routes.MapHubs();
    

    Die Datei global.asax sollte wie folgt aussehen:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    
    using System.Web.Routing;
    
    namespace MoveShapeDemo
    {
        public class Global : System.Web.HttpApplication
        {
            protected void Application_Start(object sender, EventArgs e)
            {
                RouteTable.Routes.MapHubs();
            }
        }
    }
    
  6. Als Nächstes fügen wir den Client hinzu. Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und klicken Sie dann auf Hinzufügen | Neues Element. Wählen Sie im Dialogfeld Neues Element hinzufügendie Option Html-Seite aus. Geben Sie der Seite einen entsprechenden Namen (z. B.Default.html), und klicken Sie auf Hinzufügen.

  7. Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf die soeben erstellte Seite, und klicken Sie dann auf Als Startseite festlegen.

  8. Ersetzen Sie den Standardcode auf der HTML-Seite durch den folgenden Codeausschnitt.

    Hinweis

    Vergewissern Sie sich, dass die unten angegebenen Skriptverweise mit den Paketen übereinstimmen, die Ihrem Projekt im Ordner Skripts hinzugefügt wurden. In Visual Studio 2010 entspricht die dem Projekt hinzugefügte Version von JQuery und SignalR möglicherweise nicht den folgenden Versionsnummern.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
     $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
    
                shapeModel = {
                    left: 0,
                    top: 0
                };
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moveShapeHub.server.updateModel(shapeModel);
                        }
                    });
                });
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    Der obige HTML- und JavaScript-Code erstellt ein rotes drag Div namens Shape, aktiviert das Ziehverhalten des Shapes mithilfe der jQuery-Bibliothek und verwendet das Shape-Ereignis, um die Position des Shapes an den Server zu senden.

  9. Starten Sie die Anwendung, indem Sie F5 drücken. Kopieren Sie die URL der Seite, und fügen Sie sie in ein zweites Browserfenster ein. Ziehen Sie die Form in eines der Browserfenster. die Form im anderen Browserfenster sollte verschoben werden.

    Screenshot, der zeigt, wie sich eine Form, die Sie in einem Browserfenster ziehen, in einem anderen Fenster bewegt.

Hinzufügen der Clientschleife

Da das Senden des Speicherorts des Shapes bei jedem Mausbewegungsereignis zu einer unnötigen Menge an Netzwerkdatenverkehr führt, müssen die Nachrichten vom Client gedrosselt werden. Wir verwenden die Javascript-Funktion setInterval , um eine Schleife einzurichten, die neue Positionsinformationen mit einer festen Rate an den Server sendet. Diese Schleife ist eine sehr grundlegende Darstellung einer "Spielschleife", einer wiederholt genannten Funktion, die die gesamte Funktionalität eines Spiels oder einer anderen Simulation antreibt.

  1. Aktualisieren Sie den Clientcode auf der HTML-Seite so, dass er dem folgenden Codeausschnitt entspricht.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    Mit dem obigen Update wird die updateServerModel Funktion hinzugefügt, die bei einer festen Häufigkeit aufgerufen wird. Diese Funktion sendet die Positionsdaten an den Server, wenn das moved Flag anzeigt, dass neue Positionsdaten gesendet werden.

  2. Starten Sie die Anwendung, indem Sie F5 drücken. Kopieren Sie die URL der Seite, und fügen Sie sie in ein zweites Browserfenster ein. Ziehen Sie das Shape in eines der Browserfenster; Die Form im anderen Browserfenster sollte verschoben werden. Da die Anzahl der nachrichten, die an den Server gesendet werden, gedrosselt wird, wird die Animation nicht so reibungslos wie im vorherigen Abschnitt angezeigt.

    Screenshot, der zeigt, wie eine Form, die Sie in einem Browserfenster ziehen, in einem anderen Fenster bewegt wird, wenn Sie eine Clientschleife hinzufügen.

Hinzufügen der Serverschleife

In der aktuellen Anwendung werden Nachrichten, die vom Server an den Client gesendet werden, so oft ausgegeben, wie sie empfangen werden. Dies stellt ein ähnliches Problem dar, wie es auf dem Client gesehen wurde; Nachrichten können häufiger gesendet werden, als sie benötigt werden, und die Verbindung könnte dadurch überflutet werden. In diesem Abschnitt wird beschrieben, wie Sie den Server aktualisieren, um einen Timer zu implementieren, der die Rate der ausgehenden Nachrichten drosselt.

  1. Ersetzen Sie den Inhalt von MoveShapeHub.cs durch den folgenden Codeausschnitt.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class Broadcaster
        {
            private readonly static Lazy<Broadcaster> _instance = 
                new Lazy<Broadcaster>(() => new Broadcaster());
            // We're going to broadcast to all clients a maximum of 25 times per second
            private readonly TimeSpan BroadcastInterval = 
                TimeSpan.FromMilliseconds(40); 
            private readonly IHubContext _hubContext;
            private Timer _broadcastLoop;
            private ShapeModel _model;
            private bool _modelUpdated;
    
            public Broadcaster()
            {
                // Save our hub context so we can easily use it 
                // to send to its connected clients
                _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    
                _model = new ShapeModel();
                _modelUpdated = false;
    
                // Start the broadcast loop
                _broadcastLoop = new Timer(
                    BroadcastShape, 
                    null, 
                    BroadcastInterval, 
                    BroadcastInterval);
            }
    
            public void BroadcastShape(object state)
            {
                // No need to send anything if our model hasn't changed
                if (_modelUpdated)
                {
                    // This is how we can access the Clients property 
                    // in a static hub method or outside of the hub entirely
                    _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
                    _modelUpdated = false;
                }
            }
    
            public void UpdateShape(ShapeModel clientModel)
            {
                _model = clientModel;
                _modelUpdated = true;
            }
    
            public static Broadcaster Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
        }
        
        public class MoveShapeHub : Hub
        {
            // Is set via the constructor on each creation
            private Broadcaster _broadcaster;
    
            public MoveShapeHub()
                : this(Broadcaster.Instance)
            {
            }
    
            public MoveShapeHub(Broadcaster broadcaster)
            {
                _broadcaster = broadcaster;
            }
    
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                _broadcaster.UpdateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
        
    }
    

    Der obige Code erweitert den Client, um die Broadcaster -Klasse hinzuzufügen, die die ausgehenden Nachrichten mithilfe der Timer -Klasse aus dem .NET Framework drosselt.

    Da der Hub selbst transitorisch ist (er wird jedes Mal erstellt, wenn er benötigt wird), wird als Broadcaster Singleton erstellt. Die verzögerte Initialisierung (eingeführt in .NET 4) wird verwendet, um die Erstellung zu verzögern, bis sie benötigt wird. So wird sichergestellt, dass der erste Hub instance vollständig erstellt wird, bevor der Timer gestartet wird.

    Der Aufruf der Clientfunktion UpdateShape wird dann aus der -Methode des Hubs UpdateModel verschoben, sodass er nicht mehr sofort aufgerufen wird, wenn eingehende Nachrichten empfangen werden. Stattdessen werden die Nachrichten an die Clients mit einer Rate von 25 Aufrufen pro Sekunde gesendet, die _broadcastLoop vom Timer innerhalb der Broadcaster Klasse verwaltet werden.

    Anstatt die Clientmethode direkt vom Hub aufzurufen, muss die Broadcaster -Klasse mithilfe von GlobalHosteinen Verweis auf den derzeit betriebenen Hub (_hubContext) abrufen.

  2. Starten Sie die Anwendung, indem Sie F5 drücken. Kopieren Sie die URL der Seite, und fügen Sie sie in ein zweites Browserfenster ein. Ziehen Sie das Shape in eines der Browserfenster; Die Form im anderen Browserfenster sollte verschoben werden. Es gibt keinen sichtbaren Unterschied im Browser vom vorherigen Abschnitt, aber die Anzahl der Nachrichten, die an den Client gesendet werden, wird gedrosselt.

    Screenshot, der zeigt, wie eine Form, die Sie in einem Browserfenster ziehen, in einem anderen Fenster bewegt wird, wenn Sie eine Serverschleife hinzufügen.

Hinzufügen einer reibungslosen Animation auf dem Client

Die Anwendung ist fast abgeschlossen, aber wir könnten eine weitere Verbesserung vornehmen, in der Bewegung der Form auf dem Client, während sie als Reaktion auf Servernachrichten verschoben wird. Anstatt die Position des Shapes auf die vom Server angegebene neue Position festzulegen, verwenden wir die Funktion der JQuery-UI-Bibliothek animate , um das Shape reibungslos zwischen seiner aktuellen und der neuen Position zu verschieben.

  1. Aktualisieren Sie die -Methode des updateShape Clients so, dass sie wie der folgende hervorgehobene Code aussieht:

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                 moveShapeHub.client.updateShape = function (model) {
                     shapeModel = model;
                     // Gradually move the shape towards the new location (interpolate)
                     // The updateRate is used as the duration because by the time 
                     // we get to the next location we want to be at the "last" location
                     // We also clear the animation queue so that we start a new 
                     // animation and don't lag behind.
                     $shape.animate(shapeModel, { duration: updateRate, queue: false });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    Der obige Code verschiebt die Form von der alten Position in die neue Position, die der Server im Laufe des Animationsintervalls (in diesem Fall 100 Millisekunden) angegeben hat. Jede vorherige Animation, die auf der Form ausgeführt wird, wird gelöscht, bevor die neue Animation beginnt.

  2. Starten Sie die Anwendung, indem Sie F5 drücken. Kopieren Sie die URL der Seite, und fügen Sie sie in ein zweites Browserfenster ein. Ziehen Sie das Shape in eines der Browserfenster; Die Form im anderen Browserfenster sollte verschoben werden. Die Bewegung der Form im anderen Fenster sollte weniger ruckartig erscheinen, da ihre Bewegung im Laufe der Zeit interpoliert wird, anstatt einmal pro eingehender Nachricht festgelegt zu werden.

    Screenshot, der zeigt, wie sich eine Form, die Sie in einem Browserfenster ziehen, in einem anderen Fenster bewegt, wenn Sie eine reibungslose Animation auf dem Client hinzufügen.

Weitere Schritte

In diesem Tutorial haben Sie gelernt, wie Sie eine SignalR-Anwendung programmieren, die hochfrequente Nachrichten zwischen Clients und Servern sendet. Dieses Kommunikationsparadigma ist nützlich für die Entwicklung von Online-Spielen und anderen Simulationen, z. B. das mit SignalR erstellte ShootR-Spiel.

Die vollständige Anwendung, die in diesem Tutorial erstellt wurde, kann aus dem Codekatalog heruntergeladen werden.

Weitere Informationen zu SignalR-Entwicklungskonzepten finden Sie auf den folgenden Websites für SignalR-Quellcode und -Ressourcen: