Verwendung von MongoDB im Backend der Mobile App Cloud

Ursprünglicher Autor: CarlosFigueira
  • Übersetzung


Einer der Vorteile des .NET-Backends für mobile Dienste in Azure ist die Verfügbarkeit der integrierten Unterstützung nicht nur für SQL-Datenbanken (SQL Azure), sondern auch für andere Data Warehouses.

Bei Verwendung eines Knotens. js, Sie können sich weigern, mit SQL zu arbeiten und andere mögliche Repositorys zu verwenden (wie beispielsweise in Chris Reisners Artikel über Azure Table Storage beschrieben), aber diese Funktionalität ist nicht integriert, sodass Sie selbst Code schreiben müssen.

Bei Verwendung von .NET sind die meisten Funktionen für die Arbeit mit Nicht-SQL-Repositorys bereits integriert, sodass keine "Dummy" -Tabellen wie in node.js erstellt werden müssen, nur um Datenanforderungen senden zu können.

In diesem Artikel werde ich über die MongoDB-Unterstützung sprechen und wie Sie Tabellen erstellen können, deren CRUD-Operationen direkt mit der MongoDB-Auflistung ausgeführt werden.

Datenbankeinrichtung


Wenn Sie bereits ein MongoDB-Konto haben , können Sie diesen Schritt überspringen (merken Sie sich die Verbindungszeichenfolge - wir werden sie später benötigen).

In diesem Artikel verwende ich eine Sammlung namens "Bestellungen". Wenn eine solche Sammlung nicht existiert, müssen Sie sie nicht selbst erstellen - das Backend erstellt sie automatisch.

Für diejenigen, die von vorne anfangen , erkläre ich: In diesem Artikel wird die Mongo Labs-Datenbank verwendet , die im Microsoft Azure- Portal kostenlos zur Verfügung steht (eingeschränkte Version). Um ein Konto zu erstellen, öffnen Sie das Azure-Portal, klicken Sie auf "Neu" -> "Speichern", wählen Sie das MongoLab-Add-On aus und registrieren Sie Ihr Konto. Wenn das Konto konfiguriert ist, klicken Sie auf die Schaltfläche



"Verbindungsinfo" , um den für die Verbindung zur Datenbank erforderlichen URI abzurufen. Speichern Sie es. Ihr Kontoname ist der Name der Datenbank, die wir später verwenden werden. Die Mongo-Datenbank ist konfiguriert. Wir müssen keine Sammlung erstellen, da diese erstellt wird, wenn wir zum ersten Mal versuchen, auf sie zuzugreifen.





Service-Setup


In Visual Studio gibt es keine Möglichkeit, ein Projekt mit einem Backend zu erstellen, das etwas anderes als das Entity Framework verwendet. Erstellen Sie daher ein leeres Webprojekt. Wir beginnen mit dem, was ich in meinem vorherigen Artikel zum Erstellen eines .NET-Backends von Grund auf getan habe. Anstatt jedoch das NuGet-Paket für das Azure Mobile Services .NET-Backend-Entity-Framework hinzuzufügen, fügen wir das Azure Mobile Services .NET-Backend- Mongo-

Paket hinzu . Wir fügen auch das Microsoft.Owin.Host.SystemWeb- Paket hinzu , das erforderlich ist, damit wir die Möglichkeit haben, es lokal auszuführen, um den Debugging-Prozess zu vereinfachen.

Nach der Installation beider Pakete (und ihrer Abhängigkeiten) fügen wir standardmäßig die statische Initialisierungsklasse WebApiConfig mit der Methode Register hinzu:

public static class WebApiConfig
{
    public static void Register()
    {
        ServiceConfig.Initialize(new ConfigBuilder());
    }
}

Fügen Sie der Anwendung eine globale Klasse hinzu, um den Initialisierer lokal aufzurufen:

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        WebApiConfig.Register();
    }
}

Definieren Sie das Objektmodell, das in der Datenbanksammlung gespeichert wird. Definieren Sie die Order-Klasse, die die Liste der Elemente enthält.

public class Order : DocumentData
{
    public DateTime OrderDate { get; set; }
    public string Client { get; set; }
    public List Items { get; set; }
}
public class OrderItem
{
    public string Name { get; set; }
    public double Quantity { get; set; }
    public double Price { get; set; }
}

Das Datenmodell muss die Schnittstelle auf die ItableDatagleiche Weise implementieren wie die Entity Framework-Datenmodelle.Um diese Schnittstelle in Entity Framework zu implementieren, verwenden wir eine Basisklasse, EntityData, die bei Verwendung von MongoDB der Klasse ähnlich ist.Nachdem DocumentData.müssen wir nur die Domäneneigenschaften in der Modellklasse definieren.

Tabellendefinition


Tabellen für MongoDB ähneln tatsächlich EF . Vorgänge können in demselben Szenario implementiert werden, mit Ausnahme der Situationen, in denen eine neue Steuerungsklasse für den Themenbereich implementiert werden muss, für die die MongoDomainManager- Klasse verwendet werden kann , die im Azure Mobile Services .NET-Backend-Mongo- Paket verfügbar ist .

Beachten Sie, dass Sie zur Ausführung von Operationen immer Typen direkt aus dem MongoDB-Treiber (oder einem anderen Mongo-Client) verwenden können, um Operationen zu implementieren. In allgemeinen Szenarien wird die erforderliche Implementierung jedoch von der Basisklasse TableController < T> bereitgestellt .

public class OrderController : TableController
{
    protected override void Initialize(HttpControllerContext controllerContext)
    {
        base.Initialize(controllerContext);
        var connStringName = "mongodb";
        var dbName = "MyMongoLab";
        var collectionName = "orders";
        this.DomainManager = new MongoDomainManager(connStringName, dbName, collectionName, this.Request, this.Services);
    }
    public IQueryable GetAllOrders()
    {
        return base.Query();
    }
    public Order GetOneOrder(string id)
    {
        var result = base.Lookup(id).Queryable.FirstOrDefault();
        if (result == null)
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        else
        {
            return result;
        }
    }
    public Task PostOrder(Order order)
    {
        return base.InsertAsync(order);
    }
    public Task DeleteOrder(string id)
    {
        return base.DeleteAsync(id);
    }
    public Task PatchOrder(string id, Delta patch)
    {
        return base.UpdateAsync(id, patch);
    }
}

Der erste Parameter im Konstruktor MongoDomainManagerist der Name des Elements aus dem Abschnitt < connectionStrings> in der Konfiguration, das die tatsächliche Datenbankverbindungszeichenfolge enthält (später können wir eine Funktion hinzufügen, um die tatsächliche Verbindungszeichenfolge an den Konstruktor zu übergeben).

Fügen Sie der Datei web.config den entsprechenden Abschnitt hinzu (verwenden Sie die im Azure-Portal abgerufene Verbindungszeichenfolge):


Jetzt sollte das Projekt beginnen.

Servicetests


Testen Sie den Dienst. Verwenden Sie Fiddler, um Anforderungen an den Dienst zu senden .

Lassen Sie uns zunächst sehen, was GET an uns zurückgibt:

GET http://localhost:54524/tables/order HTTP/1.1 
User-Agent: Fiddler 
Host: localhost:54524 
=-=-=-=-=-=-=-=-=-
HTTP/1.1 200 OK 
Cache-Control: no-cache 
Pragma: no-cache 
Content-Length: 2 
Content-Type: application/json; charset=utf-8 
Expires: 0 
Server: Microsoft-IIS/8.0 
X-Powered-By: ASP.NET 
Date: Mon, 14 Apr 2014 15:43:31 GMT 
[]

Nichts Unerwartetes (außer dass wir bereits eine "Bestellungen" -Kollektion haben).

Fügen Sie unserer Sammlung einige Elemente hinzu:

POST http://localhost:54524/tables/order HTTP/1.1
User-Agent: Fiddler
Host: localhost:54524
Content-Length: 211
Content-Type: application/json
{
    "client":"John Doe",
    "orderDate":"2014-04-13T00:00:00Z",
    "items":[
        { "name": "bread", "quantity": 1, "price": 1.99 },
        { "name": "milk", "quantity": 2, "price": 2.99 }
    ]
}
=-=-=-=-=-=-=-=-=-
HTTP/1.1 200 OK
Content-Length: 383
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Mon, 14 Apr 2014 15:53:13 GMT
{
  "orderDate": "2014-04-13T00:00:00Z",
  "client": "John Doe",
  "items": [
    {
      "name": "bread",
      "quantity": 1.0,
      "price": 1.99
    },
    {
      "name": "milk",
      "quantity": 2.0,
      "price": 2.99
    }
  ],
  "id": "534c0469f76e1e10c4703c2b",
  "__createdAt": "2014-04-14T15:53:12.982Z",
  "__updatedAt": "2014-04-14T15:53:12.982Z"
}

Und noch eins:

POST http://localhost:54524/tables/order HTTP/1.1
User-Agent: Fiddler
Host: localhost:54524
Content-Length: 216
Content-Type: application/json
{
    "client":"Jane Roe",
    "orderDate":"2014-02-22T00:00:00Z",
    "items":[
        { "name": "nails", "quantity": 100, "price": 3.50 },
        { "name": "hammer", "quantity": 1, "price": 12.34 }
    ]
}
=-=-=-=-=-=-=-=-=-
HTTP/1.1 200 OK
Content-Length: 387
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Mon, 14 Apr 2014 15:53:21 GMT
{
  "orderDate": "2014-02-22T00:00:00Z",
  "client": "Jane Roe",
  "items": [
    {
      "name": "nails",
      "quantity": 100.0,
      "price": 3.5
    },
    {
      "name": "hammer",
      "quantity": 1.0,
      "price": 12.34
    }
  ],
  "id": "534c0471f76e1e10c4703c2c",
  "__createdAt": "2014-04-14T15:53:21.557Z",
  "__updatedAt": "2014-04-14T15:53:21.557Z
}

Wir werden eine weitere GET-Anfrage senden, um das Ergebnis zu überprüfen:

GET http://localhost:54524/tables/order HTTP/1.1 
User-Agent: Fiddler 
Host: localhost:54524 
=-=-=-=-=-=-=-=-=-
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 239
Content-Type: application/json; charset=utf-8
Expires: 0
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Mon, 14 Apr 2014 15:55:12 GMT
[
  {
    "id": "534c0469f76e1e10c4703c2b",
    "client": "John Doe",
    "orderDate": "2014-04-13T00:00:00Z"
  },
  {
    "id": "534c0471f76e1e10c4703c2c",
    "client": "Jane Roe",
    "orderDate": "2014-02-22T00:00:00Z"
  }
]

Wir haben die Elemente früher hinzugefügt bekommen, aber nicht die komplexe Eigenschaft (Artikelliste) im Objekt.

Das Problem besteht darin, dass der Typ des Rückgabewerts der Funktion ( IQueryable Order ) nur dann komplexe Typen zurückgibt, wenn er in der Anforderung explizit angegeben ist (über den Parameter $ expand = < propertyName> ).

Es ist nützlich, eine Methode zu haben, die ein Objekt vom abfragbaren Typ zurückgibt , da Sie zusätzlich das Filtern und Sortieren (über die Parameter $ filter und $ orderby ) verwenden können.

Aus diesem Grund müssen wir entscheiden, ob das abfragbare Objekt weiterhin verwendet und der Parameter $ expand gesendet werden soll, um komplexe Typen zurückzugeben, oder ob es besser ist, zu einem anderen zurückgegebenen Typ zu wechseln.

Im letzteren Fall ist die Änderung ganz einfach:

public List GetAllOrders()
{
    return base.Query().ToList();
}

Es gibt verschiedene Möglichkeiten, Abfragen zu generieren. Am einfachsten (auf dem Server) ist es, den Client den Parameter $ expand im Header senden zu lassen , und dann muss auf dem Server nichts geändert werden.

Wir senden eine Anfrage und erhalten das gesamte Dokument zurück:

GET http://localhost:54524/tables/order?$expand=items HTTP/1.1 
User-Agent: Fiddler 
Host: localhost:54524 
=-=-=-=-=-=-=-=-=-
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 663
Content-Type: application/json; charset=utf-8
Expires: 0
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Mon, 14 Apr 2014 17:52:26 GMT
[
  {
    "id": "534c0469f76e1e10c4703c2b",
    "client": "John Doe",
    "orderDate": "2014-04-13T00:00:00Z",
    "items": [
      {
        "name": "bread",
        "quantity": 1.0,
        "price": 1.99
      },
      {
        "name": "milk",
        "quantity": 2.0,
        "price": 2.99
      }
    ]
  },
  {
    "id": "534c0471f76e1e10c4703c2c",
    "client": "Jane Roe",
    "orderDate": "2014-02-22T00:00:00Z",
    "items": [
      {
        "name": "nails",
        "quantity": 100.0,
        "price": 3.5
      },
      {
        "name": "hammer",
        "quantity": 1.0,
        "price": 12.34
      }
    ]
  }
]

Eine weitere Option ist die Verwendung des Attributs action filter, mit dem die eingehende Anforderung so geändert wird, dass der Parameter $ expand der Anforderung ständig hinzugefügt wird.

Nachfolgend finden Sie eine der möglichen Implementierungen:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class ExpandPropertyAttribute : ActionFilterAttribute
{
    string propertyName;
    public ExpandPropertyAttribute(string propertyName)
    {
        this.propertyName = propertyName;
    }
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        base.OnActionExecuting(actionContext);
        var uriBuilder = new UriBuilder(actionContext.Request.RequestUri);
        var queryParams = uriBuilder.Query.TrimStart('?').Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries).ToList();
        int expandIndex = -1;
        for (var i = 0; i < queryParams.Count; i++)
        {
            if (queryParams[i].StartsWith("$expand", StringComparison.Ordinal))
            {
                expandIndex = i;
                break;
            }
        }
        if (expandIndex < 0)
        {
            queryParams.Add("$expand=" + this.propertyName);
        }
        else
        {
            queryParams[expandIndex] = queryParams[expandIndex] + "," + propertyName;
        }
        uriBuilder.Query = string.Join("&", queryParams);
        actionContext.Request.RequestUri = uriBuilder.Uri;
    }
}

Und nachdem wir unsere Methode mit diesem Attribut markiert haben:

[ExpandProperty("Items")]
public IQueryable GetAllOrders()
{
    return base.Query();
}

Wir können Abfragen senden, die andere abfragbare Attribute verwenden , aber gleichzeitig alle Elemente des Objekts zurückgeben.

GET http://localhost:54524/tables/order?$orderby=client HTTP/1.1 
User-Agent: Fiddler 
Host: localhost:54524 
=-=-=-=-=-=-=-=-=-
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 663
Content-Type: application/json; charset=utf-8
Expires: 0
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Mon, 14 Apr 2014 18:37:27 GMT
[
  {
    "id": "534c0471f76e1e10c4703c2c",
    "client": "Jane Roe",
    "orderDate": "2014-02-22T00:00:00Z",
    "items": [
      {
        "name": "nails",
        "quantity": 100.0,
        "price": 3.5
      },
      {
        "name": "hammer",
        "quantity": 1.0,
        "price": 12.34
      }
    ]
  },
  {
    "id": "534c0469f76e1e10c4703c2b",
    "client": "John Doe",
    "orderDate": "2014-04-13T00:00:00Z",
    "items": [
      {
        "name": "bread",
        "quantity": 1.0,
        "price": 1.99
      },
      {
        "name": "milk",
        "quantity": 2.0,
        "price": 2.99
      }
    ]
  }
]


Bereitstellung


Nachdem der Dienst lokal gestartet wurde, ist alles für die Veröffentlichung in Azure bereit .

Klicken Sie nach dem Herunterladen des Veröffentlichungsprofils vom Portal mit der rechten Maustaste auf das Projekt in VS und wählen Sie "Veröffentlichen" - der Dienst wird veröffentlicht.

Und wenn wir Fiddler erneut verwenden, müssen wir zwei "order" -Elemente direkt von Azure beziehen:

GET http://blog20140413.azure-mobile.net/tables/order HTTP/1.1
User-Agent: Fiddler
Host: blog20140413.azure-mobile.net
=-=-=-=-=-=-=-=-=-
HTTP/1.1 500 Internal Server Error
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 43
Content-Type: application/json; charset=utf-8
Expires: 0
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Mon, 14 Apr 2014 18:50:22 GMT
{
  "message": "An error has occurred."
}


Irgendwas ist schief gelaufen. Standardmäßig gibt die Laufzeit keine Fehlerdetails zurück (aus Sicherheitsgründen), sodass wir die Protokolldateien im Portal überprüfen können, um festzustellen, was passiert ist. Der Fehler wird hier sein:

Exception=System.ArgumentException: No connection string named 'mongodb' could be found in the service configuration.
   at Microsoft.WindowsAzure.Mobile.Service.MongoDomainManager`1.GetMongoContext(String connectionStringName)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.WindowsAzure.Mobile.Service.MongoDomainManager`1..ctor(String connectionStringName, String databaseName, String collectionName, HttpRequestMessage request, ApiServices services)
   at MongoDbOnNetBackend.OrderController.Initialize(HttpControllerContext controllerContext)
   at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.d__0.MoveNext(), Id=6133b3eb-9851-4

Das Problem ist, dass die lokale Datei web.config , die beim Starten des Dienstes lokal verwendet wird, beim Starten des Dienstes in der Cloud nicht geeignet ist. Wir müssen die Verbindungszeichenfolge auf eine andere Weise definieren.

Aufgrund dieses Fehlers haben wir leider keine einfache Möglichkeit, die Verbindungszeichenfolge zu ermitteln (das Portal würde dies vereinfachen, aber diese Funktion ist bisher nicht verfügbar) . Daher verwenden wir eine Problemumgehung .

Gehen Sie dazu zum Portal im Abschnitt "Mobile Services" und fügen Sie auf der Registerkarte "Konfigurieren" eine neue App-Einstellung hinzu , deren Wert die Verbindungszeichenfolge ist, die wir in der Datei "web.config" definiert haben:



Nach der Initialisierung des Tabellencontrollers ändern wir die Verbindungszeichenfolge in den Diensteinstellungen basierend auf dem Wert, den wir von den Anwendungseinstellungen erhalten haben.

static bool connectionStringInitialized = false;
private void InitializeConnectionString(string connStringName, string appSettingName)
{
    if (!connectionStringInitialized)
    {
        connectionStringInitialized = true;
        if (!this.Services.Settings.Connections.ContainsKey(connStringName))
        {
            var connFromAppSetting = this.Services.Settings[appSettingName];
            var connSetting = new ConnectionSettings(connStringName, connFromAppSetting);
            this.Services.Settings.Connections.Add(connStringName, connSetting);
        }
    }
}
protected override void Initialize(HttpControllerContext controllerContext)
{
    var connStringName = "mongodb";
    var dbName = "MyMongoLab";
    var collectionName = "orders";
    // Workaround for lack of connection strings in the portal
    InitializeConnectionString(connStringName, "mongoConnectionString");
    base.Initialize(controllerContext);
    this.DomainManager = new MongoDomainManager(connStringName, dbName, collectionName, this.Request, this.Services);
}

Wenn wir den Dienst jetzt erneut bereitstellen, sollten wir in der Lage sein, Tabellendaten von Azure abzurufen:

GET http://blog20140413.azure-mobile.net/tables/order HTTP/1.1
User-Agent: Fiddler
Host: blog20140413.azure-mobile.net
x-zumo-application: cOFQkbaAmffuVRBJRpYDKHbNHbtDYG97
=-=-=-=-=-=-=-=-=-
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 663
Content-Type: application/json; charset=utf-8
Expires: 0
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Mon, 14 Apr 2014 19:21:11 GMT
[
  {
    "id": "534c0469f76e1e10c4703c2b",
    "client": "John Doe",
    "orderDate": "2014-04-13T00:00:00Z",
    "items": [
      {
        "name": "bread",
        "quantity": 1.0,
        "price": 1.99
      },
      {
        "name": "milk",
        "quantity": 2.0,
        "price": 2.99
      }
    ]
  },
  {
    "id": "534c0471f76e1e10c4703c2c",
    "client": "Jane Roe",
    "orderDate": "2014-02-22T00:00:00Z",
    "items": [
      {
        "name": "nails",
        "quantity": 100.0,
        "price": 3.5
      },
      {
        "name": "hammer",
        "quantity": 1.0,
        "price": 12.34
      }
    ]
  }
]

Schließlich stelle ich fest, dass beim lokalen Starten des Dienstes die Authentifizierung nicht standardmäßig durchgeführt wird, sodass unsere Anfrage möglicherweise keine Schlüssel sendet. Wenn Sie eine Anfrage an den Server in Azure senden, müssen Sie den Anwendungsschlüssel (Standardauthentifizierungsstufe) im Header "x-zumo-application" angeben.

Fazit


. Das Azure NET-Backend für mobile Dienste bietet eine Reihe von Speicheranbietern zum Abstrahieren von Tabellendaten.

Da die meisten der vorhandenen Beispiele die Arbeit mit dem Entity Framework (SQL Server) beschreiben, hoffe ich, dass Sie in diesem Beitrag erfahren haben, wie Sie den MongoDB-Provider zum Speichern von Daten verwenden.

Und wie immer freuen wir uns über Kommentare und Ratschläge im Blog, in den MSDN-Foren oder auf Twitter unter @AzureMobile .

Nützliche Links


Kostenlose 30-Tage-Testversion von Microsoft Azure;
Kostenloser Zugang zu Microsoft Azure Ressourcen für Start - ups , Partner , Lehrer, die MSDN - Abonnenten ;
Microsoft Azure Development Center (azurehub.ru) - Skripte, Handbücher , Beispiele, Empfehlungen zur Auswahl von Diensten und zur Entwicklung in Microsoft Azure;
Aktuelle Nachrichten Azure die Microsoft - Twitter.com/windowsazure_ru .
Die Microsoft Azure Community auf Facebook . Hier finden Sie Experten, Fotos und viele Neuigkeiten.
Microsoft Virtual Academy (MVA) - Schulungskurse
Laden Sie Visual Studio 2013 kostenlos oder als Testversion herunter

Jetzt auch beliebt: