📜 ⬆️ ⬇️

We use MongoDB in a cloud backend of mobile applications



One of the advantages of the .NET mobile services backend in Azure is that it has built-in support not only for the SQL Database (SQL Azure), but also for other data warehouses.

When using node. js, you can refuse to work with SQL and use other possible storage (for example, as it is written in Chris Reisner’s article on Azure Table Storage), but this functionality is not built-in, so you have to write some code yourself.
')
When using .NET, most of the functions for working with non-SQL storages are already integrated, so there is no need to create “dummy” tables , as in node.js, only to be able to send queries to the data.

In this article, I will talk about MongoDB support and how to create tables, CRUD operations with which will be carried out directly with the MongoDB collection.

Database Setup


If you already have a MongoDB account , you can skip this step (remember the connection string - we will need it later).

In this article I will use a collection called “orders”, if such a collection does not exist, then you don’t need to create it yourself - the backend will create it automatically.

For those who start from scratch , I explain: this article uses the Mongo Labs database , which is available on the Microsoft Azure portal for free (limited version). To create an account, go to the Azure portal, then click "New" -> "Store" and select the MongoLab add-on, and then register your account.



When the account is set up, click the "Connection info" button in order to get the URI required to connect to the database. Let's keep it. Your account name will be the name of the database that we will use later.



Mongo database is configured; we do not need to create a collection, since it will be created when we try to access it for the first time.

Service setting


In Visual Studio, there is no possibility of creating a project with a backend using something other than the Entity Framework, so we will create an empty web project. We will start with what I did in my previous article on creating a .NET backend from scratch , but instead of adding the Azure Mobile Services .NET Backend Entity Framework NuGet package, we’ll add the Azure Mobile Services .NET Backend Mongo package .

We will also add the Microsoft.Owin.Host.SystemWeb package, which is necessary in order for us to be able to run locally to facilitate the debugging process.

After installing both packages (and their dependencies), we add the initializing static WebApiConfig class with the Register method by default:

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

Add a global class to the application to locally call the initializer:

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

Define an object model that will be stored in a database collection. We define the class “Order” containing a list of elements.

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

The data model must implement the ItableData interface as well as it does for the Entity Framework data models. To implement this interface in the Entity Framework, we use the base class EntityData , which is similar to the DocumentData class when using MongoDB . After that, we can only determine the properties of the subject area in the class model.

Table definition


Tables for MongoDB are actually similar to EF . Operations can be implemented in the same scenario, excluding only those situations when we need to implement a new managing class of the domain for which we can use the MongoDomainManager class, available from the Azure Mobile Services .NET Backend Mongo package.

Please note that for execution of operations you can always use types directly from the MongoDB driver (or any other Mongo client) to implement operations, but for common scenarios the required implementation is provided by the base class TableController < T> .

 public class OrderController : TableController<Order> { protected override void Initialize(HttpControllerContext controllerContext) { base.Initialize(controllerContext); var connStringName = "mongodb"; var dbName = "MyMongoLab"; var collectionName = "orders"; this.DomainManager = new MongoDomainManager<Order>(connStringName, dbName, collectionName, this.Request, this.Services); } public IQueryable<Order> 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<Order> PostOrder(Order order) { return base.InsertAsync(order); } public Task DeleteOrder(string id) { return base.DeleteAsync(id); } public Task<Order> PatchOrder(string id, Delta<Order> patch) { return base.UpdateAsync(id, patch); } } 

The first parameter in the MongoDomainManager constructor is the name of the element from the < connectionStrings> section in the configuration that contains the actual connection string to the database (we can later add a function to pass the actual connection string to the constructor).

Add the appropriate section to the web.config file (use the connection string obtained in the Azure portal):

 <connectionStrings> <add name="mongodb" connectionString="mongodb://MyMongoLab:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-@dsNNNNNN.mongolab.com:PPPPP/MyMongoLab"/> </connectionStrings> 

Now the project should start.

Service testing


Test the service. To send requests to the service, use Fiddler .

First, let's see what GET returns to us:

 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 [] 

Nothing unexpected (except that we already have a collection of "orders").

Add a couple of elements to our collection:

 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" } 

And one more:

 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 } 

Send another GET request to check the result:

 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" } ] 

We received the elements added earlier, but did not receive difficult property (items list) in object.

The problem is that the return type of the function ( IQueryable Order ) returns complex types only if it is explicitly specified in the request (using the $ expand = < propertyName> parameter).

It is useful to have a method that returns an object of type queryable , because it additionally allows you to use filtering and sorting (via the $ filter and $ orderby parameters ).

Therefore, we must decide whether to continue using the queryable and send the $ expand parameter to return complex types, or better to switch to another return type.

In the latter case, the change is quite simple:

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

There are several options for generating queries. The simplest (on the server) is to make the client send the $ expand parameter in the header , and then nothing needs to be changed on the server.

We send the request and get back the whole document:

 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 } ] } ] 

Another option is to use the action filter attribute , which changes the incoming request so that the $ expand parameter is constantly added to the request.

Below is one of the possible implementations:

 [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; } } 

And after we mark our method with this attribute:

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

We can send queries that use other queryable attributes , but at the same time return all the elements of the object.

 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 } ] } ] 


Deployment


Now that the service is running locally, everything is ready for publishing to Azure .

After downloading the publication profile from the portal, right-click on the project in VS and select “Publish” - the service will be published.

And, if we use Fiddler again, we will need to get two “order” elements directly from Azure:

 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." } 


Something went wrong. By default, the runtime does not return error details (for security reasons), so we can check the log files in the portal and see what happened. The error will be here:

 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 

The problem is that the local web.config file used when starting the service locally is not suitable when starting the service in the cloud. We need to define the connection string in another way.

Unfortunately, because of this error, we don’t have a simple way to define a connection string (the portal would allow it to be done easily, but so far this function is not there), so we use a workaround .

To do this, go to the portal in the section of mobile services and on the tab “Configure” add a new app setting , whose value is the connection string, which we defined in the web.config file:



After initializing the table controller, we change the connection string in the service settings, based on the value we received from the application settings.

 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<Order>(connStringName, dbName, collectionName, this.Request, this.Services); } 

Now, if we deploy the service again, we should be able to get the table data from Azure:

 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 } ] } ] 

Finally, I note that with the local launch of the service, the default is not authentication, so our request may not send the keys. When sending a request to the server in Azure, you need to specify the application key (default authentication level) in the "x-zumo-application" header.

Conclusion


. NET backend for mobile services Azure offers a set of storage providers for tabular data abstraction.

Since most of the existing examples describe the work with the Entity Framework (SQL server), I hope that this post has allowed you to learn how to use the MongoDB provider for data storage.

And as usual, we welcome comments and tips on the blog, on the MSDN forums or on twitter @AzureMobile .

useful links


Free 30-day trial of Microsoft Azure;
Free access to Microsoft Azure resources for startups , partners , teachers, MSDN subscribers ;
Microsoft Azure Development Center (azurehub.ru) - scripts, tutorials, examples, recommendations on the choice of services and development on Microsoft Azure;
Latest Microsoft Azure News - Twitter.com/windowsazure_ru .
The Microsoft Azure Community on Facebook . Here you will find experts, photos and a lot of news.
Microsoft Virtual Academy (MVA) Training Courses
Download free or trial Visual Studio 2013

Source: https://habr.com/ru/post/239633/


All Articles