In this article, we’re going to cover how to set up an ASP.NET Core Web API with MongoDB as our database. MongoDB is an increasingly popular database and it is a great option for small and large scale applications. MongoDB is also a convenient database option for startups and development teams with database design limitations.

For example, suppose we’re a small team with limited resources directed to the database layer. Perhaps, the structure of our persisting data is simple. Maybe, we’re a small team with little SQL experience. This is where MongoDB could come in.

If you want to download the source code, you can do that on our ASP.NET Core and MongoDB repo.

To help us build a web API with a schema-less database, we’ll look at how to set up an ASP.NET Core application with MongoDB.

This post is divided into the following parts:

Let’s jump right in.

Introducing MongoDB

Before we get into implementing MongoDB, let’s have a quick look at what MongoDB is. Firstly, MongoDB is classified as a NoSQL database program. This means that MongoDB does not use tabular relations. It also means that MongoDB does not use SQL to manipulate its relations. MongoDB is a document-oriented database program. This means that data is stored as documents.

{
  _id: "5d8f90c0ab00ff",
  firstName: "john",
  lastName: "doe"
  course: {
    name: "history"
    grade: 94
  }
}

If we’re familiar with JSON, then we know the gist of documents. When we’re dealing with MongoDB, we need to be familiar with the following terms: databases, collection, documents, and fields. A database contains collections that contain documents that contain fields.

A little confusing?

Let’s think about it in terms of a SQL database:

  • The SQL database is like a MongoDB database
  • A table is like a MongoDB collection
  • A row is like a MongoDB document
  • And lastly, a column is like a MongoDB field

If it’s so similar, why use MongoDB?

There are several reasons why we want to use MongoDB. One of the biggest reasons is when we want to avoid a rigid schema. This gives us the flexibility to change how our data is stored without fear of breaking relationship rules defined by a rigid schema.

Now, let’s play around with MongoDB a little.

Setting up MongoDB

Using a Local MongoDB Server

First, let’s install and run the MongoDB service. Installation instructions can be found at:

https://docs.mongodb.com/manual/administration/install-community/

After installing MongoDB, we are going to create a folder which MongoDb will use as the data path. Let’s create the SchoolDb folder:

mkdir SchoolDb

Now, let’s set the data path and create the database:

mongod --dbpath ./SchoolDb

mongo

use SchoolDb

Next, let’s create two collections within our database:

db.createCollection('Courses')

db.createCollection('Students')

After that, let’s use the mongo console to create a document in the Students collection:

db.Students.insert({'FirstName': 'John', 'LastName': 'Doe', 'Major': 'Electrical Engineering'})

We can also create multiple documents with one command. Let’s go ahead and create two documents in the Courses collection:

db.Courses.insertMany([{'Name': 'Processor Design', 'Code': 'ECEN 489'}, {'Name': 'Wireless Communications', 'Code': 'ECEN 454'}])

Now, let’s look at the data stored in our collections:

db.Students.find({}).pretty()

db.Courses.find({}).pretty()

The find function finds documents within the collection calling the function. Passing an empty object as an argument tells MongoDB to find all documents. The pretty function prints the output in a more readable format. Finally, let’s take a look at the output:

Find and pretty output - ASP.NET Core - MongoDB

We have successfully entered our first database entries! The _id field is autogenerated by Mongo so it may look different every time we try this.

Using MongoDB Atlas

We can avoid the installation process by taking advantage of the free tier of the MongoDB Atlas cloud service. First, we are going to need to create an account by going to https://www.mongodb.com/cloud/atlas. Once we are all set up, let’s login and go to the dashboard where we can create a cluster:

Build a cluster - ASP.NET Core - MongoDB Atlas

Let’s take advantage of their free tier:

Create a cluster - ASP.NET Core - MongoDB Atlas

For this project, we can just create a cluster with the default settings:

Create a starter cluster - ASP.NET Core - MongoDB Atlas

To start creating a database, let’s click on the “Build a new Cluster” button:

Build a new cluster - ASP.NET Core - MongDB Atlas

Next, let’s go to “Collections” and click on “Add My Own Data”:

Collections - Add my own data - ASP.NET Core - MongoDB Atlas

 

Then, we are going to create the SchoolDb database with a Students collection:

Create a database - ASP.NET Core - MongoDB Atlas

Now that we have created a database and a collection, let’s insert a document to the Students collection:

Insert document - ASP.NET Core - MongoDB Atlas

Additionally, let’s create a document with the FirstName, LastName, and Major fields:

Insert to Collection - ASP.NET Core - MongoDB Atlas

After that, let’s create another collection called Courses by clicking on the database name and clicking on “create collection”:

Create a new collection - ASP.NET Core - MongoDB Atlas

Create collection - ASP.NET Core - MongoDB Atlas

Once we create the collection, let’s insert the documents for the Courses collection described in the previous section by repeating the process in Atlas.

Our MongoDB server is now ready to use!

Now we can dive into our ASP.NET Core project.

Configuring an ASP.NET Core Project with MongoDB

Let’s start by creating an empty web application:

dotnet new web api -o MongoDbExample

After creating our project, we are also going to install the MongoDB Driver Nuget package:

dotnet add package MongoDB.Driver

The first thing we need to configure our project with MongoDB is the connection string. If we are using a local server, we can get the connection string by running mongo in the terminal:

Connection string - ASP.NET Core - MongoDB

If we are using MongoDB’s Atlas, we can find the connection string by going to the dashboard, clicking on “connect”, and clicking on “Connect  your application”:

Connect - ASP.NET Core - MongoDB Atlas

Connect your application - ASP.NET Core - MongoDB Atlas

Finally, by setting the driver to version 2.5 or greater of C#/.Net, Atlas will produce the connection string we can use with our application:

Driver and version - ASP.NET Core - MongoDB Atlas

Let’s modify appsettings.json by adding the connection string and a few other fields:

{
  "SchoolDatabaseSettings": {
    "StudentsCollectionName": "Students",
    "CoursesCollectionName": "Courses",
    "ConnectionString": "mongodb://localhost:27017",
    "DatabaseName": "SchoolDb"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

For the rest of the example, we are going to continue using the local server keeping in mind that all the actions performed on the local server can be done on the Atlas dashboard.

Now, we are going to create a Models folder in the project root and create the SchoolDatabaseSettings.cs  class and ISchoolDatabaseSettings interface:

public class SchoolDatabaseSettings : ISchoolDatabaseSettings
{
    public string StudentsCollectionName { get; set; }
    public string CoursesCollectionName { get; set; }
    public string ConnectionString { get; set; }
    public string DatabaseName { get; set; }
}

public interface ISchoolDatabaseSettings
{
    string StudentsCollectionName { get; set; }
    string CoursesCollectionName { get; set; }
    string ConnectionString { get; set; }
    string DatabaseName { get; set; }
}

We intentionally named the class properties the same as the fields in the appsettings.json file. We are going to use this model to store the database settings and access these settings via dependency injection (DI).

To do all of this, let’s modify the ConfigureServices method in the Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<SchoolDatabaseSettings>(
        Configuration.GetSection(nameof(SchoolDatabaseSettings)));

    services.AddSingleton<ISchoolDatabaseSettings>(provider =>
        provider.GetRequiredService<IOptions<SchoolDatabaseSettings>>().Value);
    services.AddControllers();
}

We are mapping the values in the appsettings.json file to the configuration model. Then we are using the AddSingleton method to provide the configuration model to the project.

Creating the Entity Model

Now, we can create the entity models that reflect our data stored in MongoDB. In a SQL-based database, our entity model would represent a table and its properties would represent columns. With MongoDB, our entity model will represent our collection and its properties will represent fields.

So, let’s create Student.cs in the Model folder:

public class Student
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Major { get; set; }
}

Here, we have to note that Id is a required property for mapping our class to MongoDB collections. To take advantage of the BSON attributes, we must use MongoDB.Bson and MongoDB.Bson.Serialization.Attribute namespaces.

Now, let’s continue with the Course class:

public class Course
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
    public string Name { get; set; }
    public string Code { get; set; }
}

In both classes, we have defined Id as a property of types string. This allows us to represent MongoDB’s lengthy generated identifier as a string.

Creating the CRUD Operations Service

Okay, now that our model is ready, we can create a simple service with CRUD operations to be able to access the database:

public class StudentService
{
    private readonly IMongoCollection<Student> _students;

    public StudentService(ISchoolDatabaseSettings settings)
    {
        var client = new MongoClient(settings.ConnectionString);
        var database = client.GetDatabase(settings.DatabaseName);
        _students = database.GetCollection<Student>(settings.StudentsCollectionName);
    }

    public async Task<List<Student>> GetAllAsync()
    {
        return await _students.Find(s => true).ToListAsync();
    }

    public async Task<Student> GetByIdAsync(string id)
    {
        return await _students.Find<Student>(s => s.Id == id).FirstOrDefaultAsync();
    }

    public async Task<Student> CreateAsync(Student student)
    {
        await _students.InsertOneAsync(student);
        return student;
    }

    public async Task UpdateAsync(string id, Student student)
    {
        await _students.ReplaceOneAsync(s => s.Id == id, student);
    }

    public async Task DeleteAsync(string id)
    {
        await _students.DeleteOneAsync(s => s.Id == id);
    }
}

In the class constructor, we are creating a MongoClient variable to represent the mongo client defined by the connection string. Then, we are calling the GetDatabase method to represent our MongoDB database as the database variable of type MongoDatabase. Lastly, we are calling the GetCollection method to represent our MongoDB collection as the _students variable of type MongoCollection. It’s from this variable that we can use the Mongo API to perform CRUD operations on our stored data.

Within the StudentService class, we are also defining the member methods to wrap around the MongoDB driver methods. We are going to use these member methods in our API controller.

In the same manner, let’s create a CourseService class:

public class CourseService
{
    private readonly IMongoCollection<Course> _courses;

    public CourseService(ISchoolDatabaseSettings settings)
    {
        var client = new MongoClient(settings.ConnectionString);
        var database = client.GetDatabase(settings.DatabaseName);
        _courses = database.GetCollection<Course>(settings.CoursesCollectionName);
    }

    public async Task<List<Course>> GetAllAsync()
    {
        return await _courses.Find(s => true).ToListAsync();
    }

    public async Task<Course> GetByIdAsync(string id)
    {
        return await _courses.Find<Course>(c => c.Id == id).FirstOrDefaultAsync();
    }

    public async Task<Course> CreateAsync(Course course)
    {
        await _courses.InsertOneAsync(course);
        return course;
    }

    public async Task UpdateAsync(string id, Course course)
    {
        await _courses.ReplaceOneAsync(c => c.Id == id, course);
    }

    public async Task DeleteAsync(string id)
    {
        await _courses.DeleteOneAsync(c => c.Id == id);
    }
}

Now, that we have created our services, let’s register them into the application as scoped services in the ConfigureServices method in the Startup class:
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<SchoolDatabaseSettings>(
        Configuration.GetSection(nameof(SchoolDatabaseSettings)));

    services.AddSingleton<ISchoolDatabaseSettings>(provider =>
        provider.GetRequiredService<IOptions<SchoolDatabaseSettings>>().Value);

    services.AddScoped<StudentService>();
    services.AddScoped<CourseService>();
    services.AddControllers();
}

After all of these changes, let’s hook our services to our API.

Using the CRUD Service as a Web API

Let’s begin by creating StudentsController.cs which is going to be a simple API controller that uses our StudentService:

[Route("api/[controller]")]
[ApiController]
public class StudentsController : ControllerBase
{
    private readonly StudentService _studentService;

    public StudentsController(StudentService service)
    {
        _studentService = service;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Student>>> GetAll()
    {
        var students = await _studentService.GetAllAsync();
        return Ok(students);
    }

    public async Task<ActionResult<Student>> GetById(string id)
    {
        var student = await _studentService.GetByIdAsync(id);
        if (student == null)
        {
            return NotFound();
        }
        return Ok(student);
    }

    [HttpPost]
    public async Task<IActionResult> Create(Student student)
    {
        await _studentService.CreateAsync(student);
        return Ok(student);
    }

    [HttpPut]
    public async Task<IActionResult> Update(string id, Student updatedStudent)
    {
        var queriedStudent = await _studentService.GetByIdAsync(id);
        if(queriedStudent == null)
        {
            return NotFound();
        }
        await _studentService.UpdateAsync(id, updatedStudent);
        return NoContent();
    }

    [HttpDelete]
    public async Task<IActionResult> Delete(string id)
    {
        var student = await _studentService.GetByIdAsync(id);
        if (student == null)
        {
            return NotFound();
        }
        await _studentService.DeleteAsync(id);
        return NoContent();
    }
}

And that’s it, we have created an API controller to use our service.

What’s the point of the service? Why not just use the Mongo driver directly in the API controller?

The additional service layer decouples the API controller from the database. This is useful if we plan to change our database layer and is often considered good practice. To learn more about good web API practices such as the repository pattern check out our ASP.NET Core Web API series.

Now that we have everything set up, we are ready to start testing our API.

Testing the API

We can use Postman to verify that our API is working. Let’s send a GET request to /api/students:

GET request to api/students - ASP.NET Core - MongoDB

Let’s continue our test by creating a new student:

POST request to api/students - ASP.NET Core - MongoDB

Now, let’s create another one, but let’s make a mistake by changing firstName to middleName:

POST request to api/students - ASP.NET Core - MongoDB

Our API still creates a new entry in the database. The API has no way to know if all the data is valid so it just creates the entry with as much of the data as it can. Because MongoDB doesn’t require a schema, there is no validation at the database level. We opted to not have a schema, so we must implement validation in the model layer.

Validating the Model

Let’s annotate some fields as required by modifying Student.cs:

public class Student
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
    [Required(ErrorMessage = "First name is required")]
    public string FirstName { get; set; }
    [Required(ErrorMessage = "Last name is required")]
    public string LastName { get; set; }
    public string Major { get; set; }
}

We added the Required attribute to the FirstName and LastName fields to ensure that a Student object has a value for those fields.

Then let’s modify the Create action method in the StudentsController class:

[HttpPost]
public async Task<IActionResult> Create(Student student)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }
    await _studentService.CreateAsync(student);
    return Ok(student);
}

The IsValid method will validate the Student object to make sure its FirstName and LastName properties are not null. If the validation is successful, then the incoming values will bind to the model and the rest of the action will be carried out. However, if the validation rules are broken, then we are going to return a BadRequest result.

Let’s try to create another object without a firstName field:

POST with 400 response - ASP.NET Core - MongoDB

Let’s also add model validation to the Update action method in StudentsController.cs:

[HttpPut]
public async Task<IActionResult> Update(string id, Student updatedStudent)
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    var queriedStudent = await _studentService.GetByIdAsync(id);
    if(queriedStudent == null)
    {
        return NotFound();
    }

    await _studentService.UpdateAsync(id, updatedStudent);

    return NoContent();
}

Now, let’s go update the entry that was entered without a first name and give it a first name:

PUT request to api/students - ASP.NET Core - MongoDB

Furthermore, we can extract the validation code and reuse it in multiple actions by implementing action filters.

To learn more, please checkout out our article on ASP.NET Core Action Filters.

Using Relationships

Now, let’s tackle relationships. We can define a relationship within a document by referencing the id of another document. A student with two courses could have a document like this:

{
  _id: "5d8f90c0ab00ff",
  firstName: "john",
  lastName: "doe"
  courses: ["4c7e8fb09affee", "3b6d7eaf80eedd"]
}

Let’s go ahead and modify Student.cs to give it a relationship to courses:
public class Student
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }

    [Required(ErrorMessage = "First name is required")]
    public string FirstName { get; set; }

    [Required(ErrorMessage = "Last name is required")]
    public string LastName { get; set; }

    public string Major { get; set; }

    [BsonRepresentation(BsonType.ObjectId)]
    public List<string> Courses {get; set;}

    [BsonIgnore]
    public List<Course> CourseList {get; set;}
}

Because MongoDB does not require a schema, we don’t have to migrate our changes. This is a huge advantage in using NoSQL databases.

However, we must still modify our API controller to handle these changes:

public class StudentsController : ControllerBase
{
    private readonly StudentService _studentService;
    private readonly CourseService _courseService;
    public StudentsController(StudentService sService, CourseService cService)
    {
        _studentService = sService;
        _courseService = cService;
    }
    // ...
    [HttpGet("{id:length(24)}")]
    public async Task<ActionResult<Student>> GetById(string id)
    {
        var student = await _studentService.GetByIdAsync(id);
        if (student == null)
        {
            return NotFound();
        }
        if (student.Courses.Count > 0)
        {
            var tempList = new List<Course>();
            foreach (var courseId in student.Courses)
            {
                var course = await _courseService.GetByIdAsync(courseId);
                if (course != null)
                    tempList.Add(course);
            }
            student.CourseList = tempList;
        }
        return Ok(student);
    }
    // ...
}

Using the DI, we are able to use the CourseService in the API controller. In the GetById action method, we are querying the database to create a temporary variable of type List<Course>. Then we are storing this list in the CourseList property of the Student object.

Let’s create a new Student with courses. But first, we’ll need some course IDs. Since we didn’t make an API for courses, let’s query the database and get the course IDs:

Courses collection - ASP.NET Core - MongoDB

Now that we have course IDs, let’s create a new Student object using Postman:

Create student with courses - ASP.NET Core - MongoDB

The request result shows us that we successfully created the object. The result also includes the student object that was created along with the array of course IDs.

To see the full details of the courses associated with the student, let’s make a request to the GetById action:

Get student with courses - ASP.NET Core - MongoDB

As we can see, the request result gives us the student with an array containing the details of his courses.

MongoDB is an increasingly popular database and is often integrated with a gRPC service. So, if you want to learn how to use gRPC with ASP.NET Core and MongoDB, you can read the Adding gRPC to an ASP.NET Core Project and MongoDB article.

And that’s all!

Conclusion

We have successfully built an ASP.NET Core web API with MongoDB. As we have seen, MongoDB has an interface that is much simpler than writing SQL commands. Additionally, MongoDB lets us write quick backend projects by skipping schema design, database context configuration, and migration management. Remember, for even faster development try MongoDB Atlas and avoid the installation process.

In summary, we have covered:

  • A basic understanding of MongoDB
  • How to set up MongoDB
  • How to configure an ASP.NET Core project with MongoDB
  • How to build a web API with a MongoDB backend
  • How to implement validation and relationships with MongoDB