Using MongoDB with ASP.NET Web API
MongoDB is a popular NoSQL database that makes it a great backend for Web APIs which lend themselves towards a document store rather than a relational store. In this blog we show how you can use MongoDB with ASP.NET Web API to build an ApiController with support for HTTP GET, PUT, POST, and DELETE. Our sample controller is a simple contact manager Web API which builds on many of the same concepts as in the tutorial “Creating a Web API that Supports CRUD Operations” so I would recommend that you skim that for background.
In particular, the URI patterns are very similar to that of the tutorial:
Action | HTTP Method | Relative URI |
Get a list of all contacts | GET | /api/contacts |
Get a filtered list of all contacts | GET | /api/contacts?$top=2 |
Get a contacts by ID | GET | /api/contacts/id |
Create a new contact | POST | /api/contacts |
Update a contact | PUT | /api/contacts/id |
Delete a contact | DELETE | /api/contacts/id |
However, there are two differences worth noting:
- We host the sample ApiController in selfhost with a base URI of https://localhost:8080
- We have added support for OData-style queries on GET requests so that you can filter the set of contacts returned using $top, $skip, etc.
But I am getting ahead of myself – let’s get back to building the controller…
Note: Please see List of ASP.NET Web API and HttpClient Samples for the complete sample solution. Note that you still have to set up the MongoDB as described in the next section.
Prerequisites
Before you start you obviously need to download and install MongoDB. I followed the quick start instructions which has all the information you need and got me going in 10 minutes or so.
Second you need the MongoDB driver for C#. Here we use the MongoDB C# driver provided by 10gen and which is available as a NuGet package.
As an optional third step once your MongoDB installation has been set up you can use a tool like MongoVUE which allows you to browse and interact directly with the data in a MongoDB database.
Defining the Contact Type
We use the same basic Contact type used in “Creating a Web API that Supports CRUD Operations” but decorate it with an BsonIdAttribute (see the Mongo C# driver reference documentation for details) indicating which field is the id:
1: public class Contact
2: {
3: [BsonId]
4: public string Id { get; set; }
5:
6: public string Name { get; set; }
7:
8: public string Phone { get; set; }
9:
10: public string Email { get; set; }
11:
12: public DateTime LastModified { get; set; }
13: }
Using MongoVUE we can see how Contact instances show up in the MongoDB database. The sample Contact data we use in this sample looks something like this
Defining the Contact Repository
We use the common repository pattern to interact with the backend data source so the next step is to define the shape of the repository. We do this by defining an IContactRepository interface which we then implement so that it targets MongoDB. First, the interface is defined like this:
1: public interface IContactRepository
2: {
3: IEnumerable<Contact> GetAllContacts();
4:
5: Contact GetContact(string id);
6:
7: Contact AddContact(Contact item);
8:
9: bool RemoveContact(string id);
10:
11: bool UpdateContact(string id, Contact item);
12: }
Implementing the Repository
It’s when implementing IContactRepository that we use the MongoDB C# driver API to connect to the data and to do the various CRUD operations defined by IContactRepository. First we set up the connection to database and get a Contact collection. By default we use the connection string “mongodb://localhost:27017” pointing at the localhost database we set up above.
For the purpose of this sample we also reset the database and add some default entries so we have something to start with. Note that the classes MongoServer, MongoDatabase, and MongoCollection<T> are all thread-safe so they can be used simultaneously by separate threads.
1: public ContactRepository(string connection)
2: {
3: if (string.IsNullOrWhiteSpace(connection))
4: {
5: connection = "mongodb://localhost:27017";
6: }
7:
8: _server = MongoServer.Create(connection);
9: _database = _server.GetDatabase("Contacts", SafeMode.True);
10: _contacts = _database.GetCollection<Contact>("contacts");
11:
12: // Reset database and add some default entries
13: _contacts.RemoveAll();
14: for (int index = 1; index < 5; index++)
15: {
16: Contact contact1 = new Contact
17: {
18: Email = string.Format("test{0}@example.com", index),
19: Name = string.Format("test{0}", index),
20: Phone = string.Format("{0}{0}{0} {0}{0}{0} {0}{0}{0}{0}", index)
21: };
22: AddContact(contact1);
23: }
24: }
We now add the actual CRUD implementations as follows:
1: public IEnumerable<Contact> GetAllContacts()
2: {
3: return _contacts.FindAll();
4: }
5:
6: public Contact GetContact(string id)
7: {
8: IMongoQuery query = Query.EQ("_id", id);
9: return _contacts.Find(query).FirstOrDefault();
10: }
11:
12: public Contact AddContact(Contact item)
13: {
14: item.Id = ObjectId.GenerateNewId().ToString();
15: item.LastModified = DateTime.UtcNow;
16: _contacts.Insert(item);
17: return item;
18: }
19:
20: public bool RemoveContact(string id)
21: {
22: IMongoQuery query = Query.EQ("_id", id);
23: SafeModeResult result = _contacts.Remove(query);
24: return result.DocumentsAffected == 1;
25: }
26:
27: public bool UpdateContact(string id, Contact item)
28: {
29: IMongoQuery query = Query.EQ("_id", id);
30: item.LastModified = DateTime.UtcNow;
31: IMongoUpdate update = Update
32: .Set("Email", item.Email)
33: .Set("LastModified", DateTime.UtcNow)
34: .Set("Name", item.Name)
35: .Set("Phone", item.Phone);
36: SafeModeResult result = _contacts.Update(query, update);
37: return result.UpdatedExisting;
38: }
Creating the Contact Controller
That’s it for the repository so we can now implement the actual ApiController. We base the implementation on the common pattern for supporting GET, PUT, POST, and DELETE as follows:
1: public class ContactsController : ApiController
2: {
3: private static readonly IContactRepository _contacts = new ContactRepository();
4:
5: public IQueryable<Contact> Get()
6: {
7: return _contacts.GetAllContacts().AsQueryable();
8: }
9:
10: public Contact Get(string id)
11: {
12: Contact contact = _contacts.GetContact(id);
13: if (contact == null)
14: {
15: throw new HttpResponseException(HttpStatusCode.NotFound);
16: }
17:
18: return contact;
19: }
20:
21: public Contact Post(Contact value)
22: {
23: Contact contact = _contacts.AddContact(value);
24: return contact;
25: }
26:
27: public void Put(string id, Contact value)
28: {
29: if (!_contacts.UpdateContact(id, value))
30: {
31: throw new HttpResponseException(HttpStatusCode.NotFound);
32: }
33: }
34:
35: public void Delete(string id)
36: {
37: if (!_contacts.RemoveContact(id))
38: {
39: throw new HttpResponseException(HttpStatusCode.NotFound);
40: }
41: }
42: }
Note: We use IQueryable<Contact> as the return type of the Get() method. This enables automatic query-support using OData query syntax including $top and $skip. That is, if you use a URI like “/api/contacts?$top=2” with a “?$top=2” query component then you will only get the first two entries back.
Hosting the Controller
Now that we have the controller we can either host it in ASP or as selfhost. Here we use selfhost to host the controller in a simple console application but it would work exactly the same if hosted in ASP. As usual we create a HttpSelfHostConfiguration, add a route, then create a server, and start it:
1: static void Main(string[] args)
2: {
3: HttpSelfHostServer server = null;
4: try
5: {
6: // Set up server configuration
7: HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("https://localhost:8080");
8:
9: config.Routes.MapHttpRoute(
10: name: "DefaultApi",
11: routeTemplate: "api/{controller}/{id}",
12: defaults: new { id = RouteParameter.Optional }
13: );
14:
15: // Create server
16: server = new HttpSelfHostServer(config);
17:
18: // Start listening
19: server.OpenAsync().Wait();
20:
21: Console.WriteLine("Hit ENTER to exit...");
22: Console.ReadLine();
23:
24: }
25: finally
26: {
27: if (server != null)
28: {
29: // Stop listening
30: server.CloseAsync().Wait();
31: }
32: }
33: }
Note: In order to successfully start the selfhost server you have to run as admin (or configure http.sys with the appropriate URI prefix using netsh).
Trying out the Controller
When running the controller can be accessed using any HTTP client. In the full sample I show how to use HttpClient to do GET and POST but Fiddler is often very useful for trying out various combinations of GET, PUT, POST, and DELETE by using the Composer tab manually to create requests. For example, you can create a POST request like this to insert a new Contact and then hit Execute:
Similarly you can create a GET request to ask for the two first entries which will yield a result like this where the response body contains the first two Contacts:
As mentioned above, when you modify the data you can track the contents of the MongoDB database using MongoVUE.
Have fun!
Henrik
del.icio.us Tags: asp.net,webapi,mvc,rest,httpclient,mongodb
Comments
- Anonymous
February 19, 2012
Some questions:
- Can you have a default $top value? Returning ALL the documents in the database seems like a very bad idea.
- Can you return a more web friendly viewmodel instead of the actual database object and still use IQueryable<>? In most cases you have a lot of data in the documents that you don't wan't to expose to the api.
- Anonymous
February 19, 2012
- There's no out-of-the-box way to have a default $top value, but it's fairly easy to implement with an action filter (see code below). And on the operation you want the default $top value, you'd apply something like [DefaultQueryCompValues(DefaultTop = 10)]
- You can always create some data transfer objects (DTOs) and map the values from the DB to the DTO prior to returning them (_contacts.GetAllContacts().Select(c => ToDTO(c)).AsQueryable()) public class DefaultQueryCompValuesAttribute : ActionFilterAttribute { private int defaultTop = -1; public int DefaultTop { get { return this.defaultTop; } set { this.defaultTop = value; } } public override void OnActionExecuting(HttpActionContext actionContext) { if (this.DefaultTop >= 0) { HttpRequestMessage request = actionContext.Request; NameValueCollection queryParams = HttpUtility.ParseQueryString(request.RequestUri.Query); if (string.IsNullOrEmpty(queryParams.Get("$top"))) { UriBuilder uriBuilder = new UriBuilder(request.RequestUri); if (string.IsNullOrEmpty(uriBuilder.Query)) { uriBuilder.Query = "$top=" + this.DefaultTop; } else { uriBuilder.Query = uriBuilder.Query + "&$top=" + this.DefaultTop; } request.RequestUri = uriBuilder.Uri; } } } }
Anonymous
February 20, 2012
Hi Henrik, I'm currently trying out your tutorial, looks great so far. I'm wondering how to go about authenticating calls to the API. I understand how this is done using forms, but here I'm not sure what the best approach is. Would you need to set up a secure connection using https and then pass the username/password on each call? ThanksAnonymous
February 20, 2012
Nice job Henrik... You've provided a nice simple example that follows real-world needs as opposed to so many of Microsoft's "samples" which don't provide any level of separation of concerns.Anonymous
February 24, 2012
return _contacts.GetAllContacts().AsQueryable(); Doesn't that pull down ALL contacts? I don't think AsQueryable does what you think it does.Anonymous
February 25, 2012
There is cool alternative REST-framework at restservices.codeplex.com.Anonymous
February 25, 2012
Pulling all the docs to support AsQueryable is not going to scale! Mongo isn't good at paging requests, but at the very least I'd recommend using fluent mongo here as it exposes a Queryable<> that maps to native mongo commands for skip and take.Anonymous
March 02, 2012
Why is Id string and not an ObjectId in the controller methods? I have been trying this with Custom Model Binders and I can't get it to work so I am wondering whether there is some functionality I am missing. Works great with MVC.Anonymous
October 12, 2012
Have you tried Couchbase Server?Anonymous
February 03, 2014
The comment has been removedAnonymous
February 04, 2014
Hi, Thanks for the article, very good for starters like me. however, in order to remove an entry, I had to use Query.EQ("_id", new ObjectId(id)) instead of Query.EQ("_id", id). Otherwise it returned DocumentsAffected == 0 Any idea ?? GregAnonymous
February 27, 2014
This seems to actual let MongoDb handle the query and not return the full set to the app [Queryable] public IQueryable<Form> Get() { return _collection.AsQueryable(); }Anonymous
January 15, 2015
Any further thought to write WebAPi and MongoDB articles? especially best practicesAnonymous
March 26, 2015
Wow!!! Very good post. I am really so happy to read your post. I have no idea about MongoDB with ASP.NET Web API. I only knew about asp.net to read myasp.net . But now getting little knowledge. your post is so quality, so always read your every post. thanks to share.