starter ASP.NET Core Web API project with example

Starter ASP.NET Core Web API project that follows all the best practices listed above.


🛠️ Starter Project Overview

We’ll build a Product Catalog API with the following features:

🔧 Tech Stack:

  • ASP.NET Core 8

  • Entity Framework Core (In-Memory for now)

  • DTOs and Services

  • Validation

  • API Versioning

  • Swagger (OpenAPI)

  • Global Exception Handling

  • Logging

  • Async support

  • Dependency Injection

  • Clean architecture (Controllers → Services → Repository)


📁 Project Structure:

mathematica

ProductCatalogApi/ ├── Controllers/ │ └── v1/ │ └── ProductsController.cs ├── Dtos/ │ └── ProductDto.cs ├── Entities/ │ └── Product.cs ├── Interfaces/ │ └── IProductService.cs │ └── IProductRepository.cs ├── Repositories/ │ └── ProductRepository.cs ├── Services/ │ └── ProductService.cs ├── Middlewares/ │ └── ExceptionMiddleware.cs ├── Data/ │ └── ApplicationDbContext.cs ├── Program.cs └── ProductCatalogApi.csproj

✅ 1. Initialize Project

Create the project:

bash

dotnet new webapi -n ProductCatalogApi cd ProductCatalogApi

Enable nullable and implicit usings in csproj (optional):

xml

<Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings>

✅ 2. Create Product Entity

Entities/Product.cs

csharp

namespace ProductCatalogApi.Entities; public class Product { public int Id { get; set; } public string Name { get; set; } = string.Empty; public decimal Price { get; set; } }

✅ 3. Create DTO

Dtos/ProductDto.cs

csharp

using System.ComponentModel.DataAnnotations; namespace ProductCatalogApi.Dtos; public class ProductDto { public int Id { get; set; } [Required] [StringLength(100)] public string Name { get; set; } = string.Empty; [Range(0.01, 10000)] public decimal Price { get; set; } }

✅ 4. ApplicationDbContext

Data/ApplicationDbContext.cs

csharp

using Microsoft.EntityFrameworkCore; using ProductCatalogApi.Entities; namespace ProductCatalogApi.Data; public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Product> Products => Set<Product>(); }

✅ 5. Repository

Interfaces/IProductRepository.cs

csharp

using ProductCatalogApi.Entities; namespace ProductCatalogApi.Interfaces; public interface IProductRepository { Task<IEnumerable<Product>> GetAllAsync(); Task<Product?> GetByIdAsync(int id); Task<Product> AddAsync(Product product); Task UpdateAsync(Product product); Task DeleteAsync(Product product); }

Repositories/ProductRepository.cs

csharp

using Microsoft.EntityFrameworkCore; using ProductCatalogApi.Data; using ProductCatalogApi.Entities; using ProductCatalogApi.Interfaces; namespace ProductCatalogApi.Repositories; public class ProductRepository : IProductRepository { private readonly ApplicationDbContext _context; public ProductRepository(ApplicationDbContext context) { _context = context; } public async Task<IEnumerable<Product>> GetAllAsync() => await _context.Products.ToListAsync(); public async Task<Product?> GetByIdAsync(int id) => await _context.Products.FindAsync(id); public async Task<Product> AddAsync(Product product) { _context.Products.Add(product); await _context.SaveChangesAsync(); return product; } public async Task UpdateAsync(Product product) { _context.Products.Update(product); await _context.SaveChangesAsync(); } public async Task DeleteAsync(Product product) { _context.Products.Remove(product); await _context.SaveChangesAsync(); } }

✅ 6. Service Layer

Interfaces/IProductService.cs

csharp

using ProductCatalogApi.Dtos; namespace ProductCatalogApi.Interfaces; public interface IProductService { Task<IEnumerable<ProductDto>> GetAllAsync(); Task<ProductDto?> GetByIdAsync(int id); Task<ProductDto> CreateAsync(ProductDto dto); Task<bool> UpdateAsync(int id, ProductDto dto); Task<bool> DeleteAsync(int id); }

Services/ProductService.cs

csharp

using ProductCatalogApi.Dtos; using ProductCatalogApi.Entities; using ProductCatalogApi.Interfaces; namespace ProductCatalogApi.Services; public class ProductService : IProductService { private readonly IProductRepository _repo; public ProductService(IProductRepository repo) { _repo = repo; } public async Task<IEnumerable<ProductDto>> GetAllAsync() { var products = await _repo.GetAllAsync(); return products.Select(p => new ProductDto { Id = p.Id, Name = p.Name, Price = p.Price }); } public async Task<ProductDto?> GetByIdAsync(int id) { var product = await _repo.GetByIdAsync(id); if (product == null) return null; return new ProductDto { Id = product.Id, Name = product.Name, Price = product.Price }; } public async Task<ProductDto> CreateAsync(ProductDto dto) { var product = new Product { Name = dto.Name, Price = dto.Price }; var result = await _repo.AddAsync(product); return new ProductDto { Id = result.Id, Name = result.Name, Price = result.Price }; } public async Task<bool> UpdateAsync(int id, ProductDto dto) { var existing = await _repo.GetByIdAsync(id); if (existing == null) return false; existing.Name = dto.Name; existing.Price = dto.Price; await _repo.UpdateAsync(existing); return true; } public async Task<bool> DeleteAsync(int id) { var existing = await _repo.GetByIdAsync(id); if (existing == null) return false; await _repo.DeleteAsync(existing); return true; } }

✅ 7. Controller

Controllers/v1/ProductsController.cs

csharp

using Microsoft.AspNetCore.Mvc; using ProductCatalogApi.Dtos; using ProductCatalogApi.Interfaces; namespace ProductCatalogApi.Controllers.v1; [ApiController] [ApiVersion("1.0")] [Route("api/v{version:apiVersion}/[controller]")] public class ProductsController : ControllerBase { private readonly IProductService _service; public ProductsController(IProductService service) { _service = service; } [HttpGet] public async Task<IActionResult> GetAll() => Ok(await _service.GetAllAsync()); [HttpGet("{id}")] public async Task<IActionResult> Get(int id) { var product = await _service.GetByIdAsync(id); if (product == null) return NotFound(); return Ok(product); } [HttpPost] public async Task<IActionResult> Create(ProductDto dto) { if (!ModelState.IsValid) return BadRequest(ModelState); var product = await _service.CreateAsync(dto); return CreatedAtAction(nameof(Get), new { id = product.Id }, product); } [HttpPut("{id}")] public async Task<IActionResult> Update(int id, ProductDto dto) { if (!ModelState.IsValid) return BadRequest(ModelState); var updated = await _service.UpdateAsync(id, dto); return updated ? NoContent() : NotFound(); } [HttpDelete("{id}")] public async Task<IActionResult> Delete(int id) { var deleted = await _service.DeleteAsync(id); return deleted ? NoContent() : NotFound(); } }

✅ 8. Middleware for Error Handling

Middlewares/ExceptionMiddleware.cs

csharp

namespace ProductCatalogApi.Middlewares; public class ExceptionMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ExceptionMiddleware> _logger; public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (Exception ex) { _logger.LogError(ex, "Unhandled Exception"); context.Response.StatusCode = 500; await context.Response.WriteAsJsonAsync(new { error = "An unexpected error occurred." }); } } }

✅ 9. Configure Program.cs

Program.cs

csharp

using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using ProductCatalogApi.Data; using ProductCatalogApi.Interfaces; using ProductCatalogApi.Middlewares; using ProductCatalogApi.Repositories; using ProductCatalogApi.Services; var builder = WebApplication.CreateBuilder(args); // DB builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseInMemoryDatabase("ProductDb")); // Services builder.Services.AddScoped<IProductService, ProductService>(); builder.Services.AddScoped<IProductRepository, ProductRepository>(); // Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // Versioning builder.Services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ReportApiVersions = true; }); // Controllers builder.Services.AddControllers(); var app = builder.Build(); // Middlewares app.UseMiddleware<ExceptionMiddleware>(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();

✅ 10. Run the App


dotnet run

Visit:

  • Swagger: https://localhost:{port}/swagger

  • Try endpoints like:

    • GET /api/v1/products

    • POST /api/v1/products


IIS URL & Request Related Variables

 Sure! These variables are typically found in web server environments like Apache or IIS and are used by server-side applications (like PHP, Python, ASP.NET, etc.) to understand request data, file paths, and configuration settings.

Below is an explanation of each with examples where applicable.


✅ URL & Request Related Variables


1. CACHE_URL

  • Description: IIS-specific; relates to cached URLs in output caching.

  • Example: /cached-page.html

  • Use: Used internally by IIS for cached content lookup.


2. DOCUMENT_ROOT

  • Description: The absolute path to the root directory for the web server (where web content is served from).

  • Example:
    C:/inetpub/wwwroot (Windows IIS)
    /var/www/html (Apache/Linux)


3. HTTP_URL

  • Description: Often a custom environment variable; not always standard. Could represent the full HTTP URL.

  • Example: http://example.com/index.php?user=123


4. HTTP_HOST

  • Description: Host header sent by the client.

  • Example: www.example.com


5. PATH_INFO

  • Description: Extra path information provided after the script name in the URL.

  • Example: If URL is /index.php/user/123, and script is index.php, then:
    PATH_INFO = /user/123


6. PATH_TRANSLATED

  • Description: Filesystem path equivalent to PATH_INFO.

  • Example:
    C:/inetpub/wwwroot/user/123


7. QUERY_STRING

  • Description: The query string part of the URL after the ?.

  • Example:
    For /index.php?id=10&name=John,
    QUERY_STRING = id=10&name=John


8. REQUEST_FILENAME

  • Description: The absolute file path to the script being executed.

  • Example:
    /var/www/html/index.php


9. REQUEST_URI

  • Description: The full URI as requested by the client, including path and query string.

  • Example:
    /index.php?user=123


10. SCRIPT_FILENAME

  • Description: The absolute path to the script being executed (like index.php).

  • Example:
    C:/inetpub/wwwroot/index.php


11. SCRIPT_NAME

  • Description: The relative path to the script from the root of the website.

  • Example:
    /index.php


12. SCRIPT_TRANSLATED

  • Description: IIS-specific variable; maps the requested URI to a physical path.

  • Example:
    If URL is /foo/bar,
    SCRIPT_TRANSLATED = C:\inetpub\wwwroot\foo\bar


13. UNENCODED_URL

  • Description: IIS variable; shows the original URL before encoding.

  • Example:
    /path with space/file.html (not URL-encoded)


14. URL

  • Description: IIS variable; the URL portion of the request.

  • Example:
    /index.php


15. URL_PATH_INFO

  • Description: IIS variable; similar to PATH_INFO, often used in ISAPI applications.

  • Example:
    /extra/path/data



✅ IIS or Application Pool Related Variables


16. APP_POOL_ID

  • Description: Name of the IIS Application Pool that is processing the request.

  • Example:
    DefaultAppPool


17. APPL_MD_PATH

  • Description: The IIS metabase path for the application.

  • Example:
    /LM/W3SVC/1/ROOT


18. APPL_PHYSICAL_PATH

  • Description: Physical file system path to the application root.

  • Example:
    C:\inetpub\wwwroot\myapp\


19. GATEWAY_INTERFACE

  • Description: The version of the Common Gateway Interface (CGI) used.

  • Example:
    CGI/1.1


20. SERVER_SOFTWARE

  • Description: The name and version of the server software.

  • Example:
    Microsoft-IIS/10.0
    or
    Apache/2.4.41 (Unix)


21. SSI_EXEC_DISABLED

  • Description: IIS-specific; indicates that execution of commands via Server Side Includes (SSI) is disabled.

  • Example:
    true or false


✅ Summary Table

VariableDescriptionExample
CACHE_URLCached URL used by IIS/page.html
DOCUMENT_ROOTWeb root pathC:/inetpub/wwwroot
HTTP_URLFull URL (not standard)http://example.com/path
HTTP_HOSTDomain name from Host headerexample.com
PATH_INFOExtra path after script/user/123
PATH_TRANSLATEDFilesystem path from PATH_INFOC:/.../user/123
QUERY_STRINGURL query stringid=5&name=test
REQUEST_FILENAMEFull file path to requested file/var/www/index.php
REQUEST_URIEntire URI sent by client/index.php?x=1
SCRIPT_FILENAMEFull file path of the scriptC:/.../index.php
SCRIPT_NAMERelative path to the script/index.php
SCRIPT_TRANSLATEDMapped physical path of script (IIS)C:/.../foo/bar
UNENCODED_URLOriginal URL before encoding (IIS)/path with space/
URLURL part of the request (IIS)/index.php
URL_PATH_INFOExtra path info (IIS)/info/here
APP_POOL_IDName of the app pool (IIS)DefaultAppPool
APPL_MD_PATHIIS metabase path/LM/W3SVC/1/ROOT
APPL_PHYSICAL_PATHPhysical path to applicationC:/inetpub/...
GATEWAY_INTERFACECGI versionCGI/1.1
SERVER_SOFTWAREWeb server name/versionIIS/10.0
SSI_EXEC_DISABLEDWhether SSI execution is disabled (IIS)true

Explain the difference between Subject, BehaviorSubject, ReplaySubject, and AsyncSubject

 

Scenario:

You’re building a shared service that needs to hold the latest value of a user’s settings and broadcast it across multiple components.


Answer:

  • Subject: Multicast, emits only to subscribers at the time of emission.

  • BehaviorSubject: Remembers the latest emitted value, emits it immediately to new subscribers.

  • ReplaySubject: Remembers a specified number of past values and replays them to new subscribers.

  • AsyncSubject: Emits only the last value upon completion.


Example:

const subject = new BehaviorSubject<string>('default'); subject.subscribe(console.log); // logs: default subject.next('new value'); // logs: new value

Angular Core Decorators and Concepts with Examples


Angular Core Decorators and Concepts

Angular’s architecture revolves around decorators, modules, components, directives, pipes, dependency injection, data binding, and lifecycle hooks. Together, these form the “methods” or building blocks you’ll use in every Angular application. Below is a comprehensive list—if you spot something missing, it’s likely tucked away in the advanced decorators section further down.

1. Core Decorators

These decorators tell Angular how to process a class or property at runtime.

  • @NgModule Defines an Angular module, grouping components, directives, pipes, and services. Sets up the compilation context and dependency injector scope.

  • @Component A specialized @Directive with a template. Marks a class as an Angular component and provides metadata like selector, templateUrl, styles, change detection strategy, and providers.

  • @Directive Creates attribute or structural directives. You can modify the DOM or component behavior without a template.

  • @Pipe Transforms input values to output values in templates. You can define pure or impure pipes to optimize change detection.

  • @Injectable Marks a class as available for injection and can specify providedIn scope (root, a module, or a component).

2. Data Binding

Angular offers multiple binding syntaxes in templates for communication between component class and DOM.

  • Interpolation ({{ value }}) Embed component properties directly into text content.

  • Property Binding ([property]="value") Bind component data to element or directive inputs.

  • Attribute/Class/Style Binding

    • [attr.aria-label]="label"

    • [class.active]="isActive"

    • [style.width.px]="width"

  • Event Binding ((event)="handler($event)") Listen to DOM events and call component methods.

  • Two-Way Binding ([(ngModel)]="property") Combines property and event binding in one. Requires importing FormsModule or ReactiveFormsModule.

3. Components & Templates

Components are the primary UI building blocks. Their templates support:

  • Structural directives (*ngIf, *ngFor, *ngSwitchCase)

  • Template reference variables (#ref)

  • Content projection (<ng-content>)

Components also expose inputs and outputs:

  • @Input() Exposes a property to the parent component for data flow down.

  • @Output() Emits events to the parent component via EventEmitter.

  • EventEmitter<T> A generic class for emitting custom events. You call .emit(value) in the child and listen with (output)="handler($event)" in the parent.

4. Dependency Injection

Angular’s DI system is hierarchical and powerful:

  • Providers can be registered at module, component, or directive level.

  • You configure them using:

    • providedIn in @Injectable()

    • providers array in @NgModule or @Component

  • Built-in injection tokens: PLATFORM_ID, DOCUMENT, Injector

  • Constructor injection: Angular resolves and supplies dependencies when instantiating classes.

5. Directives

Beyond @Component and @Directive decorators, you get two major categories:

  1. Structural Directives Change DOM layout by adding, removing, or manipulating elements.

    • *ngIf

    • *ngFor

    • *ngSwitch

  2. Attribute Directives Change the appearance or behavior of an element.

    • Built-in: [ngClass], [ngStyle], ngModel

    • Custom: Use @Directive({ selector: '[yourDirective]' }) and inject ElementRef or Renderer2.

6. Advanced Decorators & Query APIs

Angular provides several decorators to tap into host elements, children, and lifecycle:

  • @HostBinding() Bind a property to a host element’s property or attribute.

  • @HostListener() Subscribe to DOM events on the host element.

  • @ViewChild() / @ViewChildren() Query for a child component, directive, or DOM element in the component’s view.

  • @ContentChild() / @ContentChildren() Query projected content from a parent component’s <ng-content>.

7. Lifecycle Hooks

Implement these interfaces in your component or directive class to tap into its lifecycle. Angular calls each hook at a specific time:

HookDescription
ngOnChanges()Called when input properties change.
ngOnInit()Called once after the first ngOnChanges.
ngDoCheck()Custom change-detection logic.
ngAfterContentInit()After projecting external content.
ngAfterContentChecked()After every check of projected content.
ngAfterViewInit()After initializing the component’s views and child views.
ngAfterViewChecked()After every check of the component’s views and child views.
ngOnDestroy()Cleanup before Angular destroys the directive or component.

8. Pipes

Pipes transform data in templates:

  • Built-in: DatePipe, CurrencyPipe, DecimalPipe, SlicePipe, etc.

  • Custom: Create with @Pipe({ name: 'myPipe', pure: true }) and implement transform(value, ...args).

9. Change Detection Strategies

  • Default Angular checks all components every time any asynchronous event occurs.

  • OnPush Only checks when input references change or events originate inside the component.

Quick Reference Table

CategoryKey Decorators/Features
Modules@NgModule
Components@Component, Templates, Content Projection
Directives@Directive, Structural (*ngIf, *ngFor), Attribute
Pipes@Pipe
DI@Injectable, Providers, Injection Tokens
Data BindingInterpolation, [ ], ( ), [( )], #refs
Events@Output, EventEmitter, @HostListener
Host Interaction@HostBinding
Querying@ViewChild, @ViewChildren, @ContentChild, @ContentChildren
LifecycleOnInit, OnChanges, AfterViewInit, OnDestroy, etc.
Change DetectionDefault, ChangeDetectionStrategy.OnPush

This roundup covers the essentials you mentioned—@Injectable, @Component, @Input, @Output, EventEmitter, event binding, and @Directive—plus all the core Ng decorators, binding syntaxes, lifecycle hooks, and advanced query APIs. Dive into each section in your code editor: try building tiny examples (a custom pipe, a structural directive, a host listener) to see exactly how Angular wires them together under the hood.

Can you provide examples for each Angular method?

Angular Examples for Common Decorators, Bindings, and Directives

@Injectable

The @Injectable decorator marks a class as available for dependency injection. It lets Angular’s injector know how to create instances of the class when other classes request it. By registering a service with the providedIn root scope, it becomes a singleton across the entire application.

ts
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  log(message: string): void {
    console.log(`[Logger] ${message}`);
  }
}

To consume this service, inject it into a component or another service:

ts
import { Component, OnInit } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({
  selector: 'app-root',
  template: `<h1>Check the console</h1>`
})
export class AppComponent implements OnInit {
  constructor(private logger: LoggerService) {}

  ngOnInit() {
    this.logger.log('AppComponent initialized');
  }
}

@Component

The @Component decorator defines a new UI component by attaching metadata—template, styles, and selector—to a class. This is the core building block for any Angular application.

ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-user-card',
  template: `
    <div class="card">
      <h2>{{ name }}</h2>
      <p>Age: {{ age }}</p>
    </div>
  `,
  styles: [`
    .card { border: 1px solid #ccc; padding: 1rem; border-radius: 4px; }
  `]
})
export class UserCardComponent {
  name = 'Alice';
  age = 29;
}

Use the selector <app-user-card></app-user-card> in any parent template to render the component.

@Input

The @Input decorator allows a parent component to bind values into a child component’s property. It makes the property public and bindable via property binding syntax.

ts
// child.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `<p>Message from parent: {{ message }}</p>`
})
export class ChildComponent {
  @Input() message!: string;
}
html
<!-- parent.component.html -->
<app-child [message]="parentMessage"></app-child>

When parentMessage changes in the parent class, Angular updates ChildComponent.message automatically.

@Output and EventEmitter

The @Output decorator and EventEmitter class let a child component emit events that a parent can listen to. It’s the primary way of sending data upward in the component tree.

ts
// child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child-button',
  template: `<button (click)="notifyParent()">Notify Parent</button>`
})
export class ChildButtonComponent {
  @Output() clicked = new EventEmitter<void>();

  notifyParent() {
    this.clicked.emit();
  }
}
html
<!-- parent.component.html -->
<app-child-button (clicked)="onChildClick()"></app-child-button>

The parent’s onChildClick() method is invoked whenever the child’s button is clicked.

Event Binding

Event binding connects DOM events (click, input, mouseover) to component methods. The syntax (eventName)="handler($event)" subscribes to the event and forwards its payload.

ts
// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <input (input)="onInputChange($event)" placeholder="Type something" />
    <p>You typed: {{ text }}</p>
  `
})
export class AppComponent {
  text = '';

  onInputChange(event: Event) {
    const input = event.target as HTMLInputElement;
    this.text = input.value;
  }
}

When the user types in the input field, the onInputChange method updates the text property in real time.

Creating a Custom Directive

Directives let you attach behavior to elements. An attribute directive modifies the visual appearance or behavior of its host element.

ts
import { Directive, ElementRef, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appHighlight]'
})
export class HighlightDirective {
  @Input('appHighlight') highlightColor = 'yellow';

  constructor(private el: ElementRef) {}

  @HostListener('mouseenter') onMouseEnter() {
    this.el.nativeElement.style.backgroundColor = this.highlightColor;
  }

  @HostListener('mouseleave') onMouseLeave() {
    this.el.nativeElement.style.backgroundColor = '';
  }
}

Use it like this:

html
<p appHighlight="lightblue">Hover to see highlight!</p>

The paragraph’s background toggles between transparent and lightblue on mouse enter/leave.

Additional Angular Features You Might Have Missed

  • Lifecycle Hooks: ngOnInit, ngOnDestroy, ngAfterViewInit, etc., let you tap into key moments in a component’s lifespan.

  • Pipes: Built-in (date, uppercase) or custom transforms data in templates ({{ value | currency }}).

  • Services & Modules: Organize features (NgModule) and encapsulate business logic (@Injectable).

  • Routing Guards & Resolvers: Protect routes (CanActivate) or preload data before navigation (Resolve).

Each of these plays a crucial role in building maintainable, scalable Angular applications. Let me know if you’d like code examples for any of these too!

starter ASP.NET Core Web API project with example

Starter ASP.NET Core Web API project that follows all the best practices listed above. 🛠️ Starter Project Overview We’ll build a Produc...

Best for you