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:
CRUD for contacts and contact groups
Import and export (CSV and vCard)
Search and filters with pagination
Role-based access and user profiles
File attachments for contact photos
Basic activity log and audit
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
Users(Id, UserName, Email, PasswordHash, Role, CreatedAt)
Contacts(Id, OwnerId, FirstName, LastName, Email, Phone, Company, Title, Notes, PhotoUrl, CreatedAt, UpdatedAt)
ContactGroups(Id, OwnerId, Name, Description, CreatedAt)
ContactGroupMembers(GroupId, ContactId)
ActivityLog(Id, UserId, Action, EntityType, EntityId, DataJson, CreatedAt)
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:
Task<ContactDto> CreateContact(Guid ownerId, ContactCreateDto dto)
Task<PagedResult<ContactDto>> GetContacts(Guid ownerId, ContactQuery query)
Task<ContactDto?> GetContact(Guid ownerId, Guid id)
Task UpdateContact(Guid ownerId, Guid id, ContactUpdateDto dto)
Task DeleteContact(Guid ownerId, Guid id)
Task ImportContacts(Guid ownerId, Stream csvStream)
Task<Stream> ExportContacts(Guid ownerId, ContactQuery query)
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:
Client uploads file to API or obtains presigned URL and uploads directly to blob.
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:
contact-list: search, filter, sort, pagination. Use server-side pagination.
contact-detail: show fields, photo, groups, activity log and edit button.
contact-edit: reactive form with validation; handle file input for photo.
group-management: create groups, add/remove members.
import-dialog: upload CSV and show preview before import.
Search UX: provide quick search on name/email and an advanced filter panel (company, tag, created date range).
Real-world concerns and best practices
Multi-tenant / multi-user: enforce OwnerId filters in repository layer to prevent data leakage.
Pagination: use cursor-based or offset-based pagination for lists; include total count when needed.
Validation: validate emails, phone numbers, and max field lengths on server and client.
Concurrency: use optimistic concurrency for updates (rowversion) to avoid lost edits.
Audit: write ActivityLog entries for create/update/delete, and expose them in UI.
Thumbnails: generate on upload to save bandwidth.
Rate limiting: protect import endpoints from abuse.
Testing: unit tests for services and controllers; integration tests using WebApplicationFactory.
Security recommendations
Use HTTPS and secure JWT keys (store in key vault).
Use role-based policies for admin-only actions.
Sanitize inputs and avoid reflecting user-supplied HTML.
Limit file upload types and sizes; scan images if needed.
Use CORS to allow only trusted SPA origins.
Deployment checklist
Use environment-specific configurations (DB, blob, JWT keys).
Run EF migrations during deployment.
Configure CDN for photo URLs if necessary.
Add health checks and monitoring (App Insights, Prometheus).
Configure backups for DB and blob storage.
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:
Number of contacts per user
Import success/failure rates
Upload errors and storage usage
API latency and error rates
Alert on import failures, storage near quota, or unexpected spikes.
Next steps and enhancements
Add tagging and smart groups (dynamic filters)
Add contact merge and duplicate detection
Add two-way sync with external providers (Google Contacts, Outlook)
Add contact-level permissions and shared address books
Mobile app or PWA support
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:
scaffold a runnable GitHub repo with backend and Angular frontend,
add EF Core migrations and seed sample data,
generate Angular component code (HTML, TS, CSS) for list/detail/edit,
add PNG/SVG diagrams (ERD, architecture, sequence) into the document.
Tell me which one you want next.