Building CRUD Apps with Blazor and MongoDB

Blazor is a free, open-source web framework from Microsoft that enables developers to create interactive web UIs using C# instead of JavaScript. It leverages the power of WebAssembly to run .NET code directly in web browsers while reusing server-side programming models.

MongoDB is a popular NoSQL database that stores data as flexible JSON-like documents. Its schema-less model is a great fit for the fast-changing requirements of web apps. MongoDB‘s rich query language, horizontal scalability and high performance make it an excellent choice for modern applications.

In this tutorial, we‘ll build a web app from scratch to demonstrate how to perform CRUD (Create, Read, Update, Delete) operations with Blazor and MongoDB. You‘ll learn how to:

  • Set up a Blazor WebAssembly App with a MongoDB database
  • Define data models and database context
  • Implement CRUD functionalities with Blazor and MongoDB
  • Handle concurrency, validation and error scenarios
  • Optimize performance and scalability

By the end of this tutorial, you‘ll have a good understanding of using Blazor with MongoDB to build full-stack web apps. Let‘s get started!

Prerequisites

Before we dive in, make sure you have the following installed:

  • .NET 5 SDK or later
  • Visual Studio 2019 with the ASP.NET and web development workload
  • MongoDB Server (Community Edition 4.4 or later)
  • MongoDB client tool (e.g. MongoDB Compass, Robo 3T)

Setting up a New Blazor Project

Open Visual Studio and create a new Blazor WebAssembly App project. Let‘s call it "BlazorMongoCrud".

Create a new Blazor project

In the next dialog, select the "ASP.NET Core hosted" option which will create a solution with three projects:

  • BlazorMongoCrud.Client – The Blazor WebAssembly app
  • BlazorMongoCrud.Server – The ASP.NET Core host serving the Blazor app and API endpoints
  • BlazorMongoCrud.Shared – A shared project for common code

Configure your new project

Adding MongoDB Support

To use MongoDB with .NET, we need to install the official MongoDB driver. Right-click on the BlazorMongoCrud.Server project, select "Manage NuGet Packages" and install the MongoDB.Driver package.

Install MongoDB Driver Package

Defining Data Models

Let‘s define the data models for our application. We‘ll build a simple Task Management app where users can create, view, update and delete tasks.

Create a Models folder in the BlazorMongoCrud.Shared project and add a new class named TaskModel:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System;

namespace BlazorMongoCrud.Shared.Models
{
    public class TaskModel
    {
        [BsonId]
        [BsonRepresentation(BsonType.ObjectId)]
        public string Id { get; set; }

        public string Title { get; set; }

        public string Description { get; set; }

        public bool IsComplete { get; set; }

        public DateTime CreatedAt { get; set; }
    }
}

The TaskModel class defines the structure of task documents in MongoDB. The [BsonId] attribute marks the Id property as the document‘s primary key. We also specify the [BsonRepresentation] attribute to store the Id as MongoDB‘s native ObjectId type.

Configuring Database Connection

Next, let‘s set up the connection to our MongoDB database. In the BlazorMongoCrud.Server project, open the appsettings.json file and add the MongoDB connection string:

{
  "MongoDbSettings": {
    "ConnectionString": "mongodb://localhost:27017",
    "DatabaseName": "TasksDb",
    "TasksCollectionName": "Tasks"
  }  
}

Make sure to replace the ConnectionString with your actual MongoDB server URL. The DatabaseName and TasksCollectionName specify the names of the database and collection to use for storing tasks.

To load these settings, create a new class named MongoDbSettings in the BlazorMongoCrud.Server project:

namespace BlazorMongoCrud.Server.Models
{
    public class MongoDbSettings
    {
        public string ConnectionString { get; set; }

        public string DatabaseName { get; set; }

        public string TasksCollectionName { get; set; }
    }
}

Then add the following code to the ConfigureServices method in Startup.cs:

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

    services.AddSingleton<IMongoDbSettings>(sp =>
        sp.GetRequiredService<IOptions<MongoDbSettings>>().Value);

    // other services
}

This code retrieves the MongoDB configuration settings and registers the IMongoDbSettings service for dependency injection.

Creating the Database Context

Now we need to create a database context class to interact with MongoDB. In the BlazorMongoCrud.Server project, create a new class named TaskContext:

using BlazorMongoCrud.Server.Models;
using BlazorMongoCrud.Shared.Models;
using MongoDB.Driver;

namespace BlazorMongoCrud.Server.Data
{
    public class TaskContext
    {
        private readonly IMongoDatabase _database;

        public TaskContext(IMongoDbSettings settings)
        {
            var client = new MongoClient(settings.ConnectionString);
            _database = client.GetDatabase(settings.DatabaseName);
        }

        public IMongoCollection<TaskModel> Tasks =>
            _database.GetCollection<TaskModel>("Tasks");
    }
}

The TaskContext class takes an IMongoDbSettings instance in its constructor to retrieve the connection settings. It creates a MongoClient and gets a reference to the specified database. The Tasks property provides access to the "Tasks" collection using the GetCollection method.

Implementing CRUD Operations

With the setup out of the way, let‘s implement the CRUD operations for tasks. We‘ll create an API controller to handle HTTP requests from the Blazor UI.

In the BlazorMongoCrud.Server project, create a new class named TasksController in the Controllers folder:

using BlazorMongoCrud.Server.Data;
using BlazorMongoCrud.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace BlazorMongoCrud.Server.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class TasksController : ControllerBase
    {
        private readonly TaskContext _context;

        public TasksController(TaskContext context)
        {
            _context = context;
        }

        [HttpGet]
        public async Task<IEnumerable<TaskModel>> GetTasks()
        {
            return await _context.Tasks.Find(_ => true).ToListAsync();
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<TaskModel>> GetTask(string id)
        {
            var task = await _context.Tasks.Find(t => t.Id == id).FirstOrDefaultAsync();

            if (task == null)
            {
                return NotFound();
            }

            return task;
        }

        [HttpPost]
        public async Task<ActionResult<TaskModel>> CreateTask(TaskModel task)
        {
            await _context.Tasks.InsertOneAsync(task);

            return CreatedAtAction(nameof(GetTask), new { id = task.Id }, task);
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateTask(string id, TaskModel taskUpdate)
        {
            var task = await _context.Tasks.Find(t => t.Id == id).FirstOrDefaultAsync();

            if (task == null)
            {
                return NotFound();
            }

            taskUpdate.Id = task.Id;

            await _context.Tasks.ReplaceOneAsync(t => t.Id == id, taskUpdate);

            return NoContent();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTask(string id)
        {
            var task = await _context.Tasks.Find(t => t.Id == id).FirstOrDefaultAsync();

            if (task == null)
            {
                return NotFound();
            }

            await _context.Tasks.DeleteOneAsync(t => t.Id == id);

            return NoContent();
        }
    }
}

The TasksController defines API methods for each CRUD operation:

  • GetTasks – Retrieves all tasks from the "Tasks" collection using the Find method
  • GetTask – Retrieves a single task by ID using Find and FirstOrDefaultAsync
  • CreateTask – Inserts a new task document passed in the request body using InsertOneAsync
  • UpdateTask – Updates an existing task by ID using ReplaceOneAsync
  • DeleteTask – Deletes a task by ID using DeleteOneAsync

These methods use the injected TaskContext to access the MongoDB collection. The controller methods return appropriate HTTP status codes and response data.

Building the Blazor UI

Finally, let‘s create the Blazor pages for the task management UI. In the BlazorMongoCrud.Client project, open the Pages folder and add a new Razor component named TaskList.razor:

@page "/tasks"
@inject HttpClient Http



@if (tasks == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Description</th>
                <th>Is Complete</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var task in tasks)
            {
                <tr>
                    <td>@task.Title</td>
                    <td>@task.Description</td>
                    <td>@task.IsComplete</td>
                    <td>
                        <button class="btn btn-primary"
                            @onclick="(() => ShowEditDialog(task))">
                            Edit
                        </button>
                        <button class="btn btn-danger" 
                            @onclick="(() => DeleteTask(task))">
                            Delete
                        </button>
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <button class="btn btn-success"
        @onclick="ShowCreateDialog">
        Create New Task
    </button>
}

@code {
    private IEnumerable<TaskModel> tasks;

    protected override async Task OnInitializedAsync()
    {
        tasks = await Http.GetFromJsonAsync<IEnumerable<TaskModel>>("api/tasks");
    }

    private async Task DeleteTask(TaskModel task)
    {
        await Http.DeleteAsync($"api/tasks/{task.Id}");
        tasks = await Http.GetFromJsonAsync<IEnumerable<TaskModel>>("api/tasks");
    }

    private void ShowCreateDialog()
    {
        // TODO: Implement create dialog
    }

    private void ShowEditDialog(TaskModel task)
    {
        // TODO: Implement edit dialog
    }
}

This component displays a list of tasks in a table. It injects the HttpClient to make API calls to the server. The OnInitializedAsync lifecycle method is overridden to fetch the tasks from the API when the component is initialized.

The DeleteTask method sends a DELETE request to remove a task by ID. The ShowCreateDialog and ShowEditDialog methods are placeholders for displaying modal dialogs to create and edit tasks.

You can implement the create and edit dialogs by adding additional Razor components and methods to handle form submission and API calls.

Putting It All Together

With the backend API and Blazor UI in place, you can run the application and test the CRUD functionalities. Launch the BlazorMongoCrud.Server project and navigate to the "/tasks" route to see the task list.

As you interact with the app, it will make API requests to perform the corresponding database operations. MongoDB will efficiently handle these operations, allowing for seamless data persistence and retrieval.

Tips and Considerations

Here are a few additional tips and considerations to keep in mind when building Blazor apps with MongoDB:

  • Error Handling: Implement proper error handling and logging in the API controllers and Blazor components. Display user-friendly error messages and handle exceptions gracefully.

  • Validation: Add input validation on both the client-side (Blazor) and server-side (API) to ensure data integrity and prevent invalid data from being persisted.

  • Concurrency: Consider implementing optimistic or pessimistic concurrency control mechanisms to handle scenarios where multiple users may modify the same document simultaneously.

  • Pagination: For large datasets, implement pagination to load and display data in smaller chunks. This improves performance and user experience.

  • Caching: Utilize caching mechanisms, such as in-memory caching or distributed caching (e.g., Redis), to store frequently accessed data and reduce the load on the database.

  • Indexing: Create appropriate indexes on frequently queried fields in MongoDB to optimize query performance. Analyze query patterns and create indexes accordingly.

  • Security: Implement authentication and authorization mechanisms to secure your application. Use token-based authentication (e.g., JWT) to protect API endpoints and restrict access to authorized users only.

Conclusion

In this tutorial, we explored how to build a web application using Blazor and MongoDB. We set up a Blazor WebAssembly project, integrated MongoDB, and implemented CRUD operations on the backend API. We also created a basic Blazor UI to interact with the tasks data.

Blazor and MongoDB provide a powerful combination for building modern, interactive web applications. Blazor‘s component-based architecture and C# support make it easy to create dynamic UIs, while MongoDB‘s flexibility and scalability make it suitable for handling diverse data requirements.

By following the techniques and best practices outlined in this tutorial, you can create efficient and robust CRUD applications using Blazor and MongoDB. Remember to handle errors, implement validation, and optimize performance to deliver a smooth user experience.

I hope this tutorial has been helpful in getting you started with building Blazor apps with MongoDB. Happy coding!

Similar Posts