Welcome to the fourth installment of our series! In this part, we’ll focus on performance optimizationlogging and monitoring, and CI/CD pipelines to ensure your application is fast, reliable, and ready for production. Let’s dive into the final stretch of building a robust, enterprise-ready application.

If you not have a member on Medium, read from here

Recap of Part 3

In Part 3, we covered:

  • Advanced Angular Routing: Lazy loading, route guards, and preloading strategies.
  • Caching in .NET: Using Redis to reduce database load and improve performance.
  • Deployment with Docker: Containerizing your application for consistency across environments.

1. Performance Optimization

Performance is critical for user satisfaction and scalability. Let’s explore optimization techniques for both .NET and Angular.

# Angular Performance Tips #

(a) Change Detection Strategies

Use OnPush change detection to reduce unnecessary checks in components.

// task-list.component.ts  
@Component({
selector: 'app-task-list',
templateUrl: './task-list.component.html',
changeDetection: ChangeDetectionStrategy.OnPush // Add this line
})
export class TaskListComponent {
// ...
}

(b) Lazy Load Non-Critical Modules

Split your application into smaller chunks and load them on demand.

// app-routing.module.ts  
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./features/admin/admin.module').then(m => m.AdminModule)
}
];

(c) Optimize Bundle Size

  • Use Angular’s --prod flag for production builds (enables tree-shaking and AOT compilation).
  • Audit bundles with source-map-explorer:
npm install -g source-map-explorer  
ng build --prod --source-map
source-map-explorer dist/my-app/main*.js

# .NET Performance Tips #

(a) Optimize Database Queries

Avoid N+1 query issues in Entity Framework Core by using .Include() and .AsNoTracking().

// TaskRepository.cs  
public async Task<IEnumerable<Task>> GetAllAsync() {
return await _context.Tasks
.Include(t => t.AssignedUser) // Eager-load related entities
.AsNoTracking() // Disable change tracking for read-only operations
.ToListAsync();
}

(b) Use Response Compression

Reduce payload size by enabling response compression in .NET.

// Startup.cs  
public void ConfigureServices(IServiceCollection services) {
services.AddResponseCompression(options => {
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
}

public void Configure(IApplicationBuilder app) {
app.UseResponseCompression();
}

(c) Minimize Middleware Overhead

Only use necessary middleware in the request pipeline. For example, disable detailed errors in production:

// Startup.cs  
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
} else {
app.UseExceptionHandler("/error");
}
}

2. Logging and Monitoring

Logging and monitoring are essential for diagnosing issues and maintaining application health.

# .NET Logging with Serilog #

(a) Install Serilog

dotnet add package Serilog.AspNetCore  
dotnet add package Serilog.Sinks.Elasticsearch # Optional for Elasticsearch

(b) Configure Serilog

// Program.cs  
using Serilog;

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((context, config) => {
config
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200")));
})
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());

Also, you can write Serilog config in appsettings.json

# Angular Logging with Application Insights #

(a) Install Application Insights

npm install @microsoft/applicationinsights-web  

(b) Initialize in Angular

// app/core/services/logging.service.ts  
import { Injectable } from '@angular/core';
import { ApplicationInsights } from '@microsoft/applicationinsights-web';

@Injectable({ providedIn: 'root' })
export class LoggingService {
private appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'YOUR_INSTRUMENTATION_KEY'
}
});

constructor() {
this.appInsights.loadAppInsights();
}

logError(error: Error) {
this.appInsights.trackException({ error });
}
}

# Health Checks in .NET #

Add health checks to monitor dependencies like databases and Redis.

// Startup.cs  
public void ConfigureServices(IServiceCollection services) {
services.AddHealthChecks()
.AddDbContextCheck<AppDbContext>()
.AddRedis("localhost:6379");
}

public void Configure(IApplicationBuilder app) {
app.UseEndpoints(endpoints => {
endpoints.MapHealthChecks("/health");
});
}

3. CI/CD Pipelines

Automate your deployment process with Continuous Integration and Continuous Deployment (CI/CD).

# GitHub Actions for .NET #

(a) Backend Pipeline (.github/workflows/dotnet.yml):

name: .NET CI  

on: [push]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: Publish
run: dotnet publish -c Release -o ./publish
- name: Deploy to Azure
uses: azure/webapps-deploy@v2
with:
app-name: 'my-dotnet-app'
package: ./publish

GitHub Actions for Angular

(a) Frontend Pipeline (.github/workflows/angular.yml):

name: Angular CI  

on: [push]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
- name: Install dependencies
run: npm install
- name: Build
run: npm run build -- --prod
- name: Deploy to Azure Static Web Apps
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
app_location: "/"
output_location: "dist/my-app"

What’s Next?

In Part 5, we’ll explore:

  • Microservices Architecture: Breaking down your monolith into microservices with Docker and Kubernetes.
  • Advanced Security: Implementing OAuth2, OpenID Connect, and role-based access control (RBAC).
  • Performance Tuning at Scale: Load testing, horizontal scaling, and database sharding.

Final Thoughts

Congratulations! You’ve now built a high-performanceobservable, and deployable application using .NET and Angular. By following these best practices, you’re well-equipped to tackle large-scale projects with confidence.

What topics would you like to see in future series? Let me know, and happy coding! 😊

LINK: https://javascript.plainenglish.io/building-large-scale-applications-with-net-and-angular-a-comprehensive-guide-part-4-7b3f2b992d6c