Advanced Features for Task Management Application

Advanced Features for Task Management Application

Enhance your task management app by adding user authentication, task prioritization, file uploads, and real-time notifications with .NET Core, Angular, and SQL Server.

Task management API develepment guide

Introduction

In this blog, we will extend the Task Management Application we previously built, adding advanced features to make it more powerful and user-friendly. We will implement:

  • User Authentication (with JWT tokens)
  • Task Prioritization (with priority levels)
  • File Uploads (for attaching files to tasks)
  • Real-time Notifications (using SignalR for live updates)

These features will help you take your basic task management app to the next level by incorporating essential modern web application components.

User Authentication with JWT Tokens

To enable secure user authentication, we will implement JSON Web Tokens (JWT). JWT is a compact, URL-safe means of representing claims to be transferred between two parties. It is commonly used for user authentication and information exchange in web applications.

Backend Setup (ASP.NET Core)

We need to set up JWT authentication in the .NET Core backend. Start by installing the necessary NuGet packages for authentication:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Next, update the Startup.cs file to configure authentication:

public void ConfigureServices(IServiceCollection services)
{
    // JWT Authentication Configuration
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidIssuer = Configuration["Jwt:Issuer"],
                ValidAudience = Configuration["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
            };
        });

    services.AddControllers();
}
            

Now, let's create a method to generate JWT tokens after user login:

public class AuthController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost("login")]
    public IActionResult Login([FromBody] UserLoginModel loginModel)
    {
        // Validate user credentials (from database, for example)
        if (loginModel.Username == "admin" && loginModel.Password == "password")
        {
            var token = GenerateJwtToken(loginModel.Username);
            return Ok(new { Token = token });
        }

        return Unauthorized();
    }

    private string GenerateJwtToken(string username)
    {
        var claims = new[]
        {
            new Claim(ClaimTypes.Name, username)
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddMinutes(30),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}
            

Frontend Setup (Angular)

On the frontend, use Angular to send the login credentials and store the JWT token for further use in the application. First, create an authentication service to handle login requests:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  private apiUrl = environment.apiUrl;

  constructor(private http: HttpClient) { }

  login(username: string, password: string): Observable<any> {
    return this.http.post(`${this.apiUrl}/auth/login`, { username, password });
  }

  getToken(): string {
    return localStorage.getItem('token');
  }

  setToken(token: string): void {
    localStorage.setItem('token', token);
  }

  isAuthenticated(): boolean {
    const token = this.getToken();
    return token && !this.isTokenExpired(token);
  }

  private isTokenExpired(token: string): boolean {
    const decodedToken = JSON.parse(atob(token.split('.')[1]));
    return decodedToken.exp < Date.now() / 1000;
  }
}
            

Use this service to authenticate users and store the JWT token in local storage for subsequent API calls.

Task Prioritization

Task prioritization allows users to mark tasks with different priority levels, such as Low, Medium, and High. Let’s implement this feature in both the backend and frontend.

Backend Setup

Update the Task model to include a Priority field:

public class Task
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool IsCompleted { get; set; }
    public string Priority { get; set; } // Low, Medium, High
}
            

Now, update the TaskController to accept priority levels when creating or updating a task.

[HttpPost]
public async Task<ActionResult<Task>> CreateTask(Task task)
{
    _context.Tasks.Add(task);
    await _context.SaveChangesAsync();
    return CreatedAtAction(nameof(GetTasks), new { id = task.Id }, task);
}

[HttpPut("{id}")]
public async Task<IActionResult> UpdateTask(int id, Task task)
{
    if (id != task.Id)
    {
        return BadRequest();
    }

    _context.Entry(task).State = EntityState.Modified;
    await _context.SaveChangesAsync();
    return NoContent();
}
            

Frontend Setup

On the Angular frontend, provide a dropdown to select the priority level for a task:

<select [(ngModel)]="task.priority">
  <option value="Low">Low</option>
  <option value="Medium">Medium</option>
  <option value="High">High</option>
</select>
            

Update the task model and pass the priority field in the API request when creating or updating tasks.

File Uploads

To allow users to attach files to tasks, we will implement file upload functionality. This is useful for attaching documents, images, or other files related to a task.

Backend Setup

First, create an API endpoint for file uploads in the TaskController:

[HttpPost("upload/{taskId}")]
public async Task<IActionResult> UploadFile(int taskId, IFormFile file)
{
    if (file == null || file.Length == 0)
    {
        return BadRequest("No file uploaded.");
    }

    var filePath = Path.Combine(_env.WebRootPath, "uploads", file.FileName);
    using (var stream = new FileStream(filePath, FileMode.Create))
    {
        await file.CopyToAsync(stream);
    }

    var task = await _context.Tasks.FindAsync(taskId);
    if (task == null)
    {
        return NotFound();
    }

    task.FilePath = filePath;
    _context.Tasks.Update(task);
    await _context.SaveChangesAsync();
    return Ok(new { FilePath = filePath });
}
            

Frontend Setup

On the Angular side, create a file input field in the task creation form:

<input type="file" (change)="onFileSelect($event)" />
<button (click)="uploadFile()">Upload</button>
            

In the component, handle the file selection and upload:

onFileSelect(event: any): void {
    this.selectedFile = event.target.files[0];
}

uploadFile(): void {
    const formData = new FormData();
    formData.append('file', this.selectedFile, this.selectedFile.name);
    this.taskService.uploadFile(this.task.id, formData).subscribe(response => {
        console.log('File uploaded:', response);
    });
}
            

Real-time Notifications

Implementing real-time notifications can improve the user experience. Using SignalR, you can push live updates to users whenever a task is updated or a new task is created.

Backend Setup

Install the SignalR NuGet package:

dotnet add package Microsoft.AspNetCore.SignalR

Then, create a SignalR hub for real-time communication:

public class TaskNotificationHub : Hub
{
    public async Task NotifyTaskUpdated(int taskId)
    {
        await Clients.All.SendAsync("ReceiveTaskUpdate", taskId);
    }
}
            

Inject the SignalR hub into your task update API and call it when a task is updated:

public class TaskController : ControllerBase
{
    private readonly IHubContext<TaskNotificationHub> _hubContext;

    public TaskController(IHubContext<TaskNotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateTask(int id, Task task)
    {
        // Update task logic
        await _hubContext.Clients.All.SendAsync("ReceiveTaskUpdate", task.Id);
        return NoContent();
    }
}
            

Frontend Setup

On the Angular side, use the SignalR client to receive real-time updates:

import * as signalR from "@microsoft/signalr";

export class TaskService {
    private connection: signalR.HubConnection;

    constructor() {
        this.connection = new signalR.HubConnectionBuilder()
            .withUrl("http://localhost:5000/taskNotificationHub")
            .build();
        
        this.connection.on("ReceiveTaskUpdate", (taskId: number) => {
            console.log(`Task with ID ${taskId} updated`);
            // Handle the real-time update
        });

        this.connection.start().catch(err => console.error(err));
    }
}
            

Sandip Mhaske

I’m a software developer exploring the depths of .NET, AWS, Angular, React, and digital entrepreneurship. Here, I decode complex problems, share insightful solutions, and navigate the evolving landscape of tech and finance.

Post a Comment

Previous Post Next Post