Introduction

This article explains how to build a simple, production-minded Customer Support Ticket application using ASP.NET Core for the backend and Angular for the frontend. It focuses on practical implementation: source structure, models, APIs, authentication, Angular services and components, database design, and real-world best practices.

The guide is written in clear Indian English and is aimed at senior developers who want a copy-pasteable, well-structured reference.

High-Level Overview

The application supports:

Components

Architecture Diagram (ASCII)

Code
[Angular SPA] <----HTTPS/JWT----> [ASP.NET Core Web API]
     |                                  |
     |                                  +---> [SQL Server (EF Core)]
     |                                  |
     +---> [File Uploads] --------------> [Blob Storage (Azure/AWS)]

Database Design (ERD)

Keep schema small but practical. Main tables:

Relationships

Code
Users (1) --- (M) Tickets (as CustomerId)
Users (1) --- (M) Tickets (as AssigneeId)
Tickets (1) --- (M) TicketComments
Tickets (1) --- (M) Attachments

Use FK constraints, indexes on Status, Priority, AssigneeId, and CreatedAt for efficient querying.

Backend: Project Structure

Create an ASP.NET Core Web API project named SupportBackend.

Suggested folders

Backend: Models (EF Core)

Ticket.cs

C#
public class Ticket
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public Guid CustomerId { get; set; }
    public Guid? AssigneeId { get; set; }
    public TicketPriority Priority { get; set; }
    public TicketStatus Status { get; set; }
    public DateTimeOffset CreatedAt { get; set; }
    public DateTimeOffset UpdatedAt { get; set; }

    public List<TicketComment> Comments { get; set; } = new();
    public List<Attachment> Attachments { get; set; } = new();
}

TicketComment.cs

C#
public class TicketComment
{
    public Guid Id { get; set; }
    public Guid TicketId { get; set; }
    public Guid AuthorId { get; set; }
    public string Content { get; set; }
    public DateTimeOffset CreatedAt { get; set; }
}

Attachment.cs

C#
public class Attachment
{
    public Guid Id { get; set; }
    public Guid TicketId { get; set; }
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public string BlobUrl { get; set; }
    public DateTimeOffset CreatedAt { get; set; }
}

Enums

C#
public enum TicketPriority { Low, Medium, High, Critical }
public enum TicketStatus { Open, InProgress, Resolved, Closed }

Register these in SupportDbContext and add migrations.

Backend: DTOs and Mapping

Use DTOs to avoid over-posting and for versioning. Use AutoMapper or manual mapping.

TicketCreateDto

C#
public class TicketCreateDto
{
    public string Title { get; set; }
    public string Description { get; set; }
    public TicketPriority Priority { get; set; }
}

TicketDto (returned to client)

C#
public class TicketDto
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string CustomerName { get; set; }
    public string AssigneeName { get; set; }
    public TicketPriority Priority { get; set; }
    public TicketStatus Status { get; set; }
    public DateTimeOffset CreatedAt { get; set; }
}

Backend: Services and Repositories

Keep business logic in services. Example ITicketService:

C#
public interface ITicketService
{
    Task<TicketDto> CreateTicketAsync(Guid customerId, TicketCreateDto dto);
    Task<PagedResult<TicketDto>> GetTicketsAsync(TicketQuery query);
    Task<TicketDto> GetTicketAsync(Guid ticketId);
    Task AddCommentAsync(Guid ticketId, Guid authorId, string content);
    Task AssignTicketAsync(Guid ticketId, Guid agentId);
    Task UpdateStatusAsync(Guid ticketId, TicketStatus status);
}

Implement TicketService using EF Core with transactions where necessary.

Best practice: Validate permissions in service layer (e.g., only agents can assign tickets).

Backend: Controllers (API endpoints)

Create TicketsController with endpoints:

Use [Authorize] on controllers and [Authorize(Roles = "Agent,Admin")] for assignment endpoints.

Example: Create Ticket

C#
[HttpPost]
public async Task<IActionResult> Create([FromBody] TicketCreateDto dto)
{
    var userId = User.GetUserId(); // extension to get claim
    var ticket = await _ticketService.CreateTicketAsync(userId, dto);
    return CreatedAtAction(nameof(Get), new { id = ticket.Id }, ticket);
}

Authentication and Authorization

Use ASP.NET Core Identity or a lightweight JWT solution. For many corporate apps, using Identity with EF Core is fine.

Steps

  1. Register Identity and JWT in Program.cs.

  2. Secure endpoints with [Authorize] and role policies.

  3. On login, return a JWT with sub as user id and role claims.

Important: Do not store plain passwords. Use secure hashing (Identity does this for you). Use short-lived JWTs and refresh tokens.

File Attachments

Files should be uploaded via controller to blob storage. Store metadata (BlobUrl) in Attachments table.

Controller snippet

C#
[HttpPost("{id}/attachments")]
public async Task<IActionResult> Upload(Guid id, IFormFile file)
{
    if (file.Length == 0) return BadRequest();
    var blobUrl = await _blobService.UploadFileAsync(file);
    await _ticketService.AddAttachmentAsync(id, file.FileName, file.ContentType, blobUrl);
    return Ok();
}

Security: Scan content type, limit file size, and sanitize file names.

Optional: Real-time Notifications (SignalR)

Add SignalR hub to broadcast ticket events (new ticket, assigned, status changed, comment added). Agents can subscribe to events.

Hub events: TicketCreated, TicketAssigned, CommentAdded, TicketStatusChanged.

Why: Improves agent responsiveness and dashboard UX.

Frontend: Angular Project Structure

Create Angular app support-portal and organize:

Install libraries

Code
npm install @auth0/angular-jwt
npm install @microsoft/signalr (optional for real-time)

Frontend: Authentication (JWT)

Implement AuthService to login and store token in localStorage. Use AuthInterceptor to attach the Bearer token to requests.

auth.service.ts

TypeScript
login(creds) { return this.http.post('/api/auth/login', creds).pipe(tap(res => localStorage.setItem('token', res.token))); }
logout() { localStorage.removeItem('token'); }
getToken() { return localStorage.getItem('token'); }

auth.interceptor.ts

TypeScript
intercept(req, next) {
  const token = this.auth.getToken();
  if (token) req = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } });
  return next.handle(req);
}

Frontend: Ticket Service

ticket.service.ts handles API calls and maps responses to client models.

Key methods

Use RxJS BehaviorSubject for current ticket and list updates so components can react in real time.

Frontend: Components

Create the following components:

Ticket-create form: Use reactive forms with validation rules for Title (required), Description (min length), Priority.

Ticket-detail: Keep comments in a scrollable panel and provide optimistic UI for posting comments (show locally then confirm server response).

File Upload Component

Use <input type="file"> and FormData to POST to /api/tickets/{id}/attachments.

upload function

TypeScript
const fd = new FormData();
fd.append('file', file);
this.http.post(`/api/tickets/${id}/attachments`, fd, { reportProgress: true, observe: 'events' })
  .subscribe(event => { /* handle progress and response */ });

Real-world Best Practices

Security Considerations

Deployment Checklist

Monitoring and Alerts

Monitor:

Set alerts for increased error rates, high latency, and running out of storage.

Sample Sequence: Create Ticket and Notify Agent

Code
Customer -> Angular -> POST /api/tickets
API -> Create ticket (DB) -> SignalR Hub broadcast TicketCreated to agents
Agent Dashboard (subscribed) receives event and refreshes list
Agent -> Assign ticket -> POST /api/tickets/{id}/assign -> API updates ticket -> Hub sends TicketAssigned

Testing Strategy

Example: Implementing CreateTicketAsync (Service)

C#
public async Task<TicketDto> CreateTicketAsync(Guid customerId, TicketCreateDto dto)
{
    var ticket = new Ticket
    {
        Id = Guid.NewGuid(),
        Title = dto.Title,
        Description = dto.Description,
        CustomerId = customerId,
        Priority = dto.Priority,
        Status = TicketStatus.Open,
        CreatedAt = DateTimeOffset.UtcNow,
        UpdatedAt = DateTimeOffset.UtcNow
    };

    _db.Tickets.Add(ticket);
    await _db.SaveChangesAsync();

    var result = _mapper.Map<TicketDto>(ticket);

    // notify via SignalR
    await _notificationService.TicketCreated(result);

    return result;
}

Next Steps and Extensions

Conclusion

This article provided a full, practical plan to build a Customer Support Ticket application with ASP.NET Core and Angular. It included database design, API surface, Angular structure, security, file handling, real-time notifications, and production best practices.

If you want, I can now:

Tell me which extension you would like next.