Introduction

This article describes how to build a production-ready Contact Management App using ASP.NET Core for the backend and Angular for the frontend. It is written in simple Indian English and focuses on practical, copy-pasteable code, clear project structure, and real-world best practices for security, performance, and maintainability.

The app covers:

Architecture (high level)

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

Frontend handles UI, client-side validation, and calling API endpoints. Backend handles business logic, persistence, file storage, authentication, and background jobs when needed (e.g., import parsing).

Database design (ERD)

Main tables

Relationships (text)

Users (1) --- (M) Contacts
Users (1) --- (M) ContactGroups
ContactGroups (1) --- (M) ContactGroupMembers --- (M) Contacts

Index OwnerId, Email, LastName, and CreatedAt for efficient filters and search.

Backend: Project structure and packages

Create ASP.NET Core Web API project (example ContactApi). Use these packages:

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
dotnet add package CsvHelper
dotnet add package Azure.Storage.Blobs    // optional for photo storage

Suggested folder layout

/ContactApi
  /Controllers
  /Data (DbContext, Migrations)
  /Models (Entities)
  /DTOs
  /Services (business logic)
  /Repositories
  /Helpers
  /BackgroundJobs
  Program.cs

Keep controllers thin and move business rules to services. Use repository abstraction for data access if preferred.

Backend: EF Core models (examples)

Contact.cs

public class Contact
{
    public Guid Id { get; set; }
    public Guid OwnerId { get; set; }
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public string? Email { get; set; }
    public string? Phone { get; set; }
    public string? Company { get; set; }
    public string? Title { get; set; }
    public string? Notes { get; set; }
    public string? PhotoUrl { get; set; }
    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
    public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
}

ContactGroup.cs

public class ContactGroup
{
    public Guid Id { get; set; }
    public Guid OwnerId { get; set; }
    public string Name { get; set; } = string.Empty;
    public string? Description { get; set; }
    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
    public List<ContactGroupMember> Members { get; set; } = new();
}

ContactGroupMember.cs

public class ContactGroupMember
{
    public Guid GroupId { get; set; }
    public ContactGroup Group { get; set; } = null!;
    public Guid ContactId { get; set; }
    public Contact Contact { get; set; } = null!;
}

Backend: DTOs and mapping

Use DTOs to avoid over-posting and to version the API. Use AutoMapper for mapping.

ContactCreateDto

public class ContactCreateDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string? Email { get; set; }
    public string? Phone { get; set; }
    public string? Company { get; set; }
    public string? Title { get; set; }
    public string? Notes { get; set; }
}

ContactDto (returned to clients)

public class ContactDto { public Guid Id; public string FirstName; public string LastName; public string? Email; public string? Phone; public string? PhotoUrl; }

Backend: Services

Create IContactService and ContactService with methods:

Keep validation (unique email per owner, format checks) here. Also write to ActivityLog when important actions happen.

Backend: Controllers (sample)

ContactsController

[ApiController]
[Route("api/[controller]")]
[Authorize]
public class ContactsController : ControllerBase
{
    private readonly IContactService _service;
    public ContactsController(IContactService service) => _service = service;

    [HttpPost]
    public async Task<IActionResult> Create([FromBody] ContactCreateDto dto)
    {
        var ownerId = User.GetUserId();
        var contact = await _service.CreateContact(ownerId, dto);
        return CreatedAtAction(nameof(Get), new { id = contact.Id }, contact);
    }

    [HttpGet]
    public async Task<IActionResult> List([FromQuery] ContactQuery query)
    {
        var ownerId = User.GetUserId();
        var page = await _service.GetContacts(ownerId, query);
        return Ok(page);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid id)
    {
        var ownerId = User.GetUserId();
        var contact = await _service.GetContact(ownerId, id);
        if (contact == null) return NotFound();
        return Ok(contact);
    }
}

User.GetUserId() is a helper extension to parse sub claim.

Import & Export (CSV and vCard)

CSV Import: Use CsvHelper for robust parsing. Validate rows and provide a summary after import (created, updated, skipped with reasons). For large files, process import as a background job (Hangfire) and provide progress via ActivityLog or SignalR.

vCard Export: Build vCard text lines for contacts and return as text/vcard attachment.

Photo upload and storage

Use blob storage (Azure Blob or S3) for photos. Steps:

  1. Client uploads file to API or obtains presigned URL and uploads directly to blob.

  2. API stores blob path in PhotoUrl and returns accessible URL or presigned URL.

Validate image type, restrict max size, and create thumbnails asynchronously. Store PhotoUrl as CDN or blob path.

Frontend: Angular project structure

Suggested layout:

/src/app
  /core (auth, http interceptors, models)
  /contacts (services, components: list, detail, edit, import)
  /shared (components: search, pagination, file-uploader)
  /features (groups, import-export)

Install helpful packages

npm install @auth0/angular-jwt ngx-file-drop papaparse

papaparse helps parse CSV on client if you prefer client-side validation.

Frontend: ContactService (detailed)

contact.service.ts responsibilities:

Example methods

create(dto) { return this.http.post('/api/contacts', dto); }
list(query) { return this.http.get('/api/contacts', { params: query }); }
get(id) { return this.http.get(`/api/contacts/${id}`); }
update(id, dto) { return this.http.put(`/api/contacts/${id}`, dto); }
uploadPhoto(id, file) { const fd = new FormData(); fd.append('photo', file); return this.http.post(`/api/contacts/${id}/photo`, fd); }
importCsv(file) { const fd = new FormData(); fd.append('file', file); return this.http.post('/api/contacts/import', fd); }
exportCsv(query) { return this.http.get('/api/contacts/export', { params: query, responseType: 'blob' }); }

Frontend: Components

Create components:

Search UX: provide quick search on name/email and an advanced filter panel (company, tag, created date range).

Real-world concerns and best practices

Security recommendations

Deployment checklist

Sample flow (sequence)

User -> Angular (create contact) -> POST /api/contacts -> API validates -> Save DB -> Upload photo to blob -> ActivityLog created -> UI shows created contact

Example: Import CSV (backend sketch)

[HttpPost("import")]
public async Task<IActionResult> Import(IFormFile file)
{
    var ownerId = User.GetUserId();
    if (file.Length == 0) return BadRequest();

    using var reader = new StreamReader(file.OpenReadStream());
    using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
    var records = csv.GetRecords<ContactImportRow>().ToList();
    // validate rows; for large files enqueue a background job to process
    var result = await _service.BulkImport(ownerId, records);
    return Ok(result);
}

Monitoring and metrics

Track:

Alert on import failures, storage near quota, or unexpected spikes.

Next steps and enhancements

Conclusion

This article provides a complete, production-aware plan to build a Contact Management App with ASP.NET Core and Angular. It covers data model, API design, Angular integration, file handling, import/export, security, and operational concerns.

If you want, I can now:

Tell me which one you want next.