Part 7 is complete — the BlogApp now has a working full-stack integration layer connecting Angular to ASP.NET Core to SQL Server with authentication, real-time updates, file handling, and production-quality error handling. This final lesson reviews the complete architecture, verifies the middleware and interceptor pipelines, and prepares for Parts 8 (Testing) and 9 (Capstone).
Complete Data Flow Trace
// ── Complete trace: User submits a new post (admin flow) ──────────────────
// Layer 1: Angular PostFormComponent
// User fills the form and clicks "Publish"
// onSave('published') is called
// PostsApiService.create(request) is called
// HttpClient.post('/api/posts', body) fires
// Layer 2: Angular HTTP Interceptor Pipeline
// loggingInterceptor: → POST /api/posts (logged)
// loadingInterceptor: loading.start() (spinner appears)
// retryInterceptor: (pass-through, no error yet)
// authInterceptor: Authorization: Bearer eyJ... added, withCredentials: true
// errorInterceptor: (pass-through, response not yet received)
// Layer 3: ASP.NET Core Middleware Pipeline
// HTTPS Redirection: enforces HTTPS
// Routing: matches POST /api/posts → PostsController.Create
// CORS: adds CORS headers to response
// Authentication: validates JWT Bearer → sets ClaimsPrincipal
// Authorization: [Authorize] passes (valid JWT)
// Exception Handler: wraps the rest (catches any unhandled exceptions)
// Layer 4: PostsController.Create
// Model binding: deserialises request body to CreatePostRequest
// FluentValidation: validates title, slug, body
// _currentUser.UserId: injected from JWT claims
// _posts.CreateAsync(request, ct): calls service
// Layer 5: PostsService.CreateAsync
// Checks slug uniqueness (throws ConflictException if taken)
// Creates Post entity
// await _db.SaveChangesAsync(ct): EF Core generates INSERT SQL
// Layer 6: EF Core → SQL Server
// INSERT INTO Posts (Title, Slug, Body, AuthorId, Status, CreatedAt, UpdatedAt)
// VALUES (@p0, @p1, @p2, @p3, @p4, SYSUTCDATETIME(), SYSUTCDATETIME())
// AuditInterceptor fires: sets CreatedAt, CreatedBy, UpdatedAt, UpdatedBy
// Returns new Post with Id and RowVersion
// Layer 5 (return): PostsService maps Post → PostDto
// Layer 4 (return): PostsController
// return CreatedAtAction(nameof(GetBySlug), { slug }, postDto)
// Response: 201 Created, Location: /api/posts/my-new-post, body: PostDto JSON
// Layer 3 (response): ASP.NET Core pipes response back through middleware
// ETag header added by controller
// Layer 2 (response): Angular Interceptors (reverse order)
// errorInterceptor: status 201 — no action
// authInterceptor: 201 — no action (not 401)
// retryInterceptor: 201 — no action
// loadingInterceptor: finalize() fires → loading.stop() (spinner hides)
// loggingInterceptor: ← 201 /api/posts (52ms) (logged)
// Layer 1 (result): Angular PostFormComponent.onSave()
// next(postDto): navigate to /admin/posts, show success toast
// form.markAsPristine(): clear unsaved changes flag
Production Readiness Final Checklist
-- ── ASP.NET Core Production Checklist ──────────────────────────────────────
-- [ ] HTTPS enforced (UseHttpsRedirection + HSTS)
-- [ ] CORS locked down (explicit origins, not AllowAnyOrigin)
-- [ ] JWT validation enabled (ValidateIssuer, ValidateAudience, ValidateLifetime)
-- [ ] JWT signing key in environment variable / Key Vault (not in source)
-- [ ] Global exception handler returns ProblemDetails (not HTML 500 pages)
-- [ ] TraceId in all error responses (correlates with logs)
-- [ ] Serilog (or equivalent) writing structured logs to storage
-- [ ] Health check endpoint wired (/api/health → load balancer probe)
-- [ ] EF Core RCSI enabled on database
-- [ ] NOLOCK not used anywhere
-- [ ] Request size limits configured for uploads
-- [ ] Rate limiting configured (prevent abuse)
-- [ ] sa account disabled in SQL Server
-- [ ] Application login uses least-privilege (db_datareader + db_datawriter)
-- ── Angular Production Checklist ──────────────────────────────────────────
-- [ ] ng build --configuration=production succeeds
-- [ ] environment.prod.ts has correct API URL (not localhost)
-- [ ] enableDebugTools: false in production environment
-- [ ] Auth debug overlay not visible in production
-- [ ] GlobalErrorHandler registered and connected to monitoring
-- [ ] CSP (Content Security Policy) headers configured on serving host
-- [ ] HTTPS-only (no mixed content)
-- [ ] Images have width/height attributes (no CLS)
-- [ ] LCP image has loading="eager" (not lazy)
-- [ ] Angular Universal / SSR considered for SEO (optional)
X-Skip-Loading header used to bypass the global loading spinner is a convention internal to the Angular app. Ensure the API’s CORS configuration allows this custom header in WithHeaders(): WithHeaders("Authorization", "Content-Type", "X-Skip-Loading", ...). If the CORS preflight rejects the custom header, all requests with it will fail in cross-origin deployments. Test the custom header explicitly in the CORS preflight verification.🎓 Part 7 Complete — What’s Next
With Part 7 complete, the BlogApp has a full working vertical slice from Angular to SQL Server. Every layer is connected, every request is handled consistently, and the development experience is polished. Parts 8 and 9 build on this foundation:
| Part | Focus | Chapters |
|---|---|---|
| Part 8 — Unit Testing | xUnit, Moq, Angular Jasmine, Cypress E2E, CI pipeline | 77–84 |
| Part 9 — Capstone | Clean Architecture, CQRS/MediatR, classified website project | 85–90 |
Common Mistakes
Mistake 1 — Skipping production checklist verification (silent misconfiguration goes live)
❌ Wrong — deploying without verifying CORS, HTTPS, JWT validation; users hit CORS errors or insecure endpoints in production.
✅ Correct — run the production checklist against the deployed environment; test from a real browser (not just localhost).
Mistake 2 — Not adding custom headers to CORS AllowedHeaders (preflight rejects the header)
❌ Wrong — X-Skip-Loading used in Angular but not in WithHeaders(); CORS preflight blocks requests with that header.
✅ Correct — add every custom header to the production CORS policy’s WithHeaders() call.