
Using MongoDB in the cloud backend of mobile applications
- Transfer

One of the advantages of the .NET backend for mobile services in Azure is the availability of built-in support not only for 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 repositories (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 functions for working with non-SQL repositories are already integrated, so there is no need to create “dummy” tables , as in node.js, only for the ability to send data requests.
In this article I will talk about MongoDB support and how you can create tables whose CRUD operations will be performed directly with the MongoDB collection.
Database setup
If you already have a MongoDB account , then 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 do not 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 configured, click

“Connection info” in order to get the URI needed to connect to the database. Save it. Your account name will be the name of the database, which we will use later. The Mongo database is configured; we don’t need to create a collection, as it will be created when we first try to access it.

Service Setup
In Visual Studio there is no way to create a project with a backend that uses something other than the Entity Framework, so create an empty web project. We'll 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 will add the Azure Mobile Services .NET Backend Mongo
package . We also add the Microsoft.Owin.Host.SystemWeb package , which is necessary so that we have the opportunity to run locally to facilitate the debugging process.
After installing both packages (and their dependencies), we add the initializing static class WebApiConfig 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 call the initializer locally:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
WebApiConfig.Register();
}
}
Define the object model that will be stored in the database collection. Define the Order class containing the list of elements.
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; }
}
The data model must implement the interface in the
ItableData
same way as for the Entity Framework data models. To implement this interface in the Entity Framework, we use a base class EntityData
,
which, when using MongoDB, is similar to the class. DocumentData
.
After that, we can only define the properties of the domainTable 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 control class for the subject area, 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 necessary implementation is provided by the base class TableController < T> .
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);
}
}
The first parameter in the constructor
MongoDomainManager
is the name of the element from the < connectionStrings> section in the configuration that contains the actual database connection string (later we can add a function to pass the actual connection string to the designer). Add the appropriate section to the web.config file (use the connection string obtained on the Azure portal):
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 “orders” collection).
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
}
Let's 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 got the elements added earlier, but did not get the complex property (items list) in the object.
The problem is that the function return type ( IQueryable Order ) returns complex types only if it is explicitly specified in the request (via 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 (through the parameters $ filter and $ orderby ).
Therefore, we must decide whether to continue to use the queryable and send the $ expand parameter to return complex types or whether it is better to switch to another returned type.
In the latter case, the change is quite simple:
public List 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 will need to be changed on the server.
We send a 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 will change the incoming request so that the $ expand parameter is constantly added to the request.
The following is one possible implementation:
[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 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 publication on 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 have 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 on 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 locally when starting the service, 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 determine the connection string (the portal would make it easy, but so far this function is not there), so we use a workaround .
To do this, go to the portal in the mobile services section and add a new app setting on the “Configure” tab , whose value is the connection string that 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 that we got 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(connStringName, dbName, collectionName, this.Request, this.Services);
}
Now, if we deploy the service again, we should be able to get 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 when the service is launched locally, authentication is not performed by default, therefore, our request may not send 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
. The Azure NET backend for mobile services offers a set of storage providers to abstract table data.
Since most of the existing examples describe working with the Entity Framework (SQL server), I hope that this post allowed you to learn how to use the MongoDB provider to store data.
And as usual, we welcome comments and tips on the blog, on 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, subscribers of MSDN ;
Microsoft Azure Development Center (azurehub.ru) - scripts, guides, examples, recommendations on choosing services and development on Microsoft Azure;
Latest news Azure the Microsoft - Twitter.com/windowsazure_ru .
The Microsoft Azure Community on Facebook . Here you will find experts, photos and lots of news.
Microsoft Virtual Academy (MVA) Training Courses
Download Free or Trial Visual Studio 2013