Building SmartTask Manager API

Building SmartTask Manager API: ASP.NET Core Web API + EF Core

Building SmartTask Manager API

This step-by-step guide walks you through creating a complete **Web API to manage tasks** in a real-world style. You’ll use **ASP.NET Core Web API** for building the service endpoints and **Entity Framework Core (EF Core)** for database interaction. This API is designed to be a robust backend, ready to be integrated with any frontend, including a full-stack Blazor application.


Step 1: Create the Project

First, let's set up our ASP.NET Core Web API project. Open your terminal or command prompt and run the following commands:


dotnet new webapi -n SmartTaskManager.Api
cd SmartTaskManager.Api
        

✅ This command creates a new ASP.NET Core Web API project named `SmartTaskManager.Api`. By default, this project comes with Swagger (OpenAPI) enabled for easy API testing and a sample `WeatherForecastController`. For our task manager, you can remove the `WeatherForecastController.cs` and `WeatherForecast.cs` files.


Step 2: Define the Model

Next, we'll define the C# model that represents a single task item. This model will be used by both our API and our database.

Create a new folder named `Models` in your `SmartTaskManager.Api` project, and then add a new C# class file named `TaskItem.cs` inside it.


// Models/TaskItem.cs
using System.ComponentModel.DataAnnotations; // Required for Data Annotations

namespace SmartTaskManager.Api.Models
{
    public class TaskItem
    {
        public int Id { get; set; } // Primary Key (EF Core convention)

        [Required(ErrorMessage = "Task title is required.")] // Data Annotation for validation
        public string Title { get; set; } = string.Empty; // Initialize to prevent null warnings

        public string Description { get; set; } = string.Empty; // Optional description

        public string AssignedTo { get; set; } = string.Empty; // Who the task is assigned to

        public DateTime DueDate { get; set; } = DateTime.Today; // Deadline for the task

        public bool IsCompleted { get; set; } = false; // Status of the task
    }
}
        

💡 **Explanation:**

  • `Id`: This property will automatically be recognized by EF Core as the **primary key** for the `TaskItem` entity, and its value will typically be auto-incremented by the database.
  • `Title`: Marked with `[Required]`, meaning a task must always have a title.
  • `Description`: An optional field for more details about the task.
  • `AssignedTo`: Could store a user's name or ID, indicating who is responsible for the task.
  • `DueDate`: A `DateTime` property to track deadlines.
  • `IsCompleted`: A boolean flag to easily track the completion status of a task.


Step 3: Set Up EF Core DbContext

Now, let's configure Entity Framework Core to interact with our database. We'll start by installing the necessary NuGet packages and creating our `DbContext` class.

🔧 Install EF Core Packages

Open your terminal in the `SmartTaskManager.Api` project directory and run:


dotnet add package Microsoft.EntityFrameworkCore.SqlServer
        

This package provides the SQL Server provider for EF Core.

🧠 Create Data/AppDbContext.cs

Create a new folder named `Data` in your `SmartTaskManager.Api` project, and then add a new C# class file named `AppDbContext.cs` inside it.


// Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore; // Required for DbContext
using SmartTaskManager.Api.Models; // Required for TaskItem model

namespace SmartTaskManager.Api.Data
{
    public class AppDbContext : DbContext
    {
        // Constructor to pass DbContextOptions to the base class
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}

        // DbSet property for our TaskItem entity, representing the "Tasks" table in the database
        public DbSet<TaskItem> Tasks { get; set; }
    }
}
        

💡 **Explanation:**

  • `AppDbContext` inherits from `Microsoft.EntityFrameworkCore.DbContext`, which is the main class for interacting with your database using EF Core.
  • `public DbSet Tasks { get; set; }`: This property tells EF Core to create and manage a database table named `Tasks` (by convention) that will store `TaskItem` objects.


Step 4: Register DbContext in Program.cs

Now we need to register our `AppDbContext` with the ASP.NET Core dependency injection container and provide a database connection string.

Open `SmartTaskManager.Api/Program.cs` and add the following code:


// Program.cs
using SmartTaskManager.Api.Data; // For AppDbContext
using Microsoft.EntityFrameworkCore; // For UseSqlServer

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

// Add EF Core DbContext and configure it to use SQL Server
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); // Enable Swagger UI

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(); // Enable Swagger UI
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers(); // Maps controller actions to routes

app.Run();
        

🔧 **Add connection string in `appsettings.json`:** Open `SmartTaskManager.Api/appsettings.json` and add the `ConnectionStrings` section.


// appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=SmartTaskDb;Trusted_Connection=True;MultipleActiveResultSets=true;"
  }
}
        

**Note:** `Server=(localdb)\\mssqllocaldb` points to a local SQL Server Express LocalDB instance, which is convenient for development. `Database=SmartTaskDb` is the name of our database. `Trusted_Connection=True` uses Windows Authentication. `MultipleActiveResultSets=true` is often useful for certain EF Core scenarios, though not strictly required for this basic setup.


Step 5: Create the Database

With our model and `DbContext` defined, we can now use EF Core migrations to create the database schema.

🔧 Install EF CLI if not available

If you haven't already, install the Entity Framework Core command-line tools globally:


dotnet tool install --global dotnet-ef
        

🔧 Run migrations and update database

Open your terminal in the `SmartTaskManager.Api` project directory and run these commands:


dotnet ef migrations add InitialCreate

dotnet ef database update
        

The `Add-Migration InitialCreate` command creates a new migration file that represents the initial state of your database schema based on your `TaskItem` model. The `Update-Database` command then applies this migration to your SQL Server instance, creating the `SmartTaskDb` database and the `Tasks` table within it.


Step 6: Create the TasksController

Now we'll create our API controller that exposes CRUD operations for our tasks.

Create a new folder named `Controllers` in your `SmartTaskManager.Api` project, and then add a new C# class file named `TasksController.cs` inside it.


// Controllers/TasksController.cs
using Microsoft.AspNetCore.Mvc;
using SmartTaskManager.Api.Data; // For AppDbContext
using SmartTaskManager.Api.Models; // For TaskItem model
using Microsoft.EntityFrameworkCore; // For ToListAsync, FindAsync, Entry, State

namespace SmartTaskManager.Api.Controllers
{
    [ApiController] // Indicates this is an API controller
    [Route("api/[controller]")] // Sets the base route for this controller (e.g., /api/tasks)
    public class TasksController : ControllerBase
    {
        private readonly AppDbContext _context; // Inject our DbContext

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

        // GET: api/tasks
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TaskItem>>> GetTasks()
        {
            // Returns all tasks from the database
            return await _context.Tasks.ToListAsync();
        }

        // GET: api/tasks/{id}
        [HttpGet("{id}")] // Route with an ID parameter
        public async Task<ActionResult<TaskItem>> GetTask(int id)
        {
            // Finds a single task by its ID
            var task = await _context.Tasks.FindAsync(id);
            if (task == null)
            {
                return NotFound(); // Returns 404 Not Found if task doesn't exist
            }
            return task; // Returns 200 OK with the task
        }

        // POST: api/tasks
        [HttpPost]
        public async Task<ActionResult<TaskItem>> CreateTask(TaskItem task)
        {
            // Adds the new task to the database
            _context.Tasks.Add(task);
            await _context.SaveChangesAsync(); // Saves changes to the database

            // Returns 201 Created status with the location of the new resource
            return CreatedAtAction(nameof(GetTask), new { id = task.Id }, task);
        }

        // PUT: api/tasks/{id}
        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateTask(int id, TaskItem updatedTask)
        {
            if (id != updatedTask.Id)
            {
                return BadRequest(); // Returns 400 Bad Request if ID in route doesn't match ID in body
            }

            // Marks the entity as modified so EF Core knows to update it
            _context.Entry(updatedTask).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync(); // Saves changes
            }
            catch (DbUpdateConcurrencyException)
            {
                // Handle concurrency conflicts if the task was deleted by another process
                if (!_context.Tasks.Any(e => e.Id == id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }
            return NoContent(); // Returns 204 No Content for successful update
        }

        // DELETE: api/tasks/{id}
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTask(int id)
        {
            // Finds the task to delete
            var task = await _context.Tasks.FindAsync(id);
            if (task == null)
            {
                return NotFound(); // Returns 404 if task doesn't exist
            }

            // Removes the task from the database
            _context.Tasks.Remove(task);
            await _context.SaveChangesAsync(); // Saves changes
            return NoContent(); // Returns 204 No Content for successful deletion
        }
    }
}
        

Step 7: Test the API

Now that our API is built, let's test it using Swagger UI, which is automatically configured in ASP.NET Core Web API projects.

Run your API project from the terminal:


dotnet run
        

Once the application starts, open your browser and navigate to the Swagger UI endpoint, typically:
https://localhost:5001/swagger (or similar port if different).

In the Swagger UI, you can:

  • **GET /api/Tasks:** Try it out to see an empty list initially.
  • **POST /api/Tasks:** Use the "Try it out" feature to create new tasks. Provide a JSON body for the task (e.g., `{"title": "Learn Blazor", "description": "Complete the Blazor course", "assignedTo": "Raushan", "dueDate": "2025-08-31T00:00:00", "isCompleted": false}`).
  • **GET /api/Tasks:** Call this again to see your newly created tasks.
  • **GET /api/Tasks/{id}:** Retrieve a specific task by its ID.
  • **PUT /api/Tasks/{id}:** Update an existing task.
  • **DELETE /api/Tasks/{id}:** Delete a task.

💡 Optional Filters:

You can enhance your `TasksController` to support filtering tasks based on criteria like `assignedTo` or `IsCompleted`. Add the following new `HttpGet` action to your `TasksController.cs`:


// Controllers/TasksController.cs (Add this method)
// GET: api/tasks/filter?assignedTo=Raushan&completed=false
[HttpGet("filter")] // Custom route for filtering
public async Task<ActionResult<IEnumerable<TaskItem>>> Filter(string? assignedTo, bool? completed)
{
    var query = _context.Tasks.AsQueryable(); // Start with all tasks

    // Apply filter for AssignedTo if provided
    if (!string.IsNullOrEmpty(assignedTo))
    {
        query = query.Where(t => t.AssignedTo == assignedTo);
    }

    // Apply filter for IsCompleted if provided
    if (completed.HasValue)
    {
        query = query.Where(t => t.IsCompleted == completed.Value);
    }

    // Execute the query and return the filtered list
    return await query.ToListAsync();
}
        

This `Filter` method allows you to query your tasks with optional parameters, providing more flexibility for your frontend.


✅ You’ve Built a Real Web API!

Congratulations! You now have a fully working ASP.NET Core Web API that can manage tasks, complete with CRUD operations, database persistence via Entity Framework Core, and optional filtering capabilities. This API is ready to be connected to any frontend application, including your Blazor WebAssembly client, to build a complete full-stack solution.

Raushan Ranjan

Microsoft Certified Trainer

.NET | Azure | Power Platform | WPF | Qt/QML Developer

Power BI Developer | Data Analyst

📞 +91 82858 62455
🌐 raushanranjan.azurewebsites.net
🔗 linkedin.com/in/raushanranjan

Comments

Popular posts from this blog

Blazor: Building Web Apps with C# - Introduction

Blazor WebAssembly Hosted App Tutorial: Envelope Tracker System

Securing MVC-based Applications Using Blazor