<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: kirandeepjassal-crypto</title>
    <description>The latest articles on Forem by kirandeepjassal-crypto (@kirandeepjassalcrypto).</description>
    <link>https://forem.com/kirandeepjassalcrypto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3948965%2F92a8ebec-5c78-46dc-b19b-babd45c794b0.png</url>
      <title>Forem: kirandeepjassal-crypto</title>
      <link>https://forem.com/kirandeepjassalcrypto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kirandeepjassalcrypto"/>
    <language>en</language>
    <item>
      <title>RAG Explained: Feed Your Enterprise Data to an LLM with Azure OpenAI + Azure SQL (.NET)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Mon, 25 May 2026 16:41:09 +0000</pubDate>
      <link>https://forem.com/kirandeepjassalcrypto/rag-explained-feed-your-enterprise-data-to-an-llm-with-azure-openai-azure-sql-net-4d16</link>
      <guid>https://forem.com/kirandeepjassalcrypto/rag-explained-feed-your-enterprise-data-to-an-llm-with-azure-openai-azure-sql-net-4d16</guid>
      <description>&lt;p&gt;Every team wants to put an LLM on top of their own data — but they hit two walls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The model doesn't know your data.&lt;/strong&gt; GPT-4o has never seen your handbook, your support tickets, or your product catalog. Ask it a company-specific question and you get a generic answer or a confident hallucination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can't send confidential data to a public API.&lt;/strong&gt; Data residency, customer contracts, and trade secrets make that a non-starter.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;RAG (Retrieval-Augmented Generation)&lt;/strong&gt; solves both. You retrieve the most relevant chunks from &lt;em&gt;your&lt;/em&gt; data, put them in the prompt as context, and the model answers using only that — with citations, and without your data ever leaving your Azure tenant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow (Azure OpenAI + Azure SQL)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ingestion (once):&lt;/strong&gt; chunk your docs, embed each chunk with Azure OpenAI, and store the vectors in Azure SQL's native &lt;code&gt;VECTOR&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Query (every question):&lt;/strong&gt; embed the question with the same model, run a &lt;code&gt;VECTOR_DISTANCE&lt;/code&gt; search in Azure SQL for the top chunks, build a grounded prompt with citations, then let Azure OpenAI (GPT-4o) answer from that context only.&lt;/p&gt;

&lt;p&gt;Two rules that make or break it: the embedding model &lt;strong&gt;must&lt;/strong&gt; match between ingestion and query, and you need a distance threshold so the system says "I don't know" instead of hallucinating on weak context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the 2-minute explainer
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/v7BZV7mDTj0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Go deeper
&lt;/h2&gt;

&lt;p&gt;The full production guide — the Azure SQL schema, the ingestion and query services in C#, Managed Identity, cost, latency, and the 8 pitfalls that ruin RAG in production:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://prepstack.co.in/blog/rag-azure-openai-azure-sql-csharp-guide" rel="noopener noreferrer"&gt;Production RAG with Azure OpenAI + Azure SQL (C# guide)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>azure</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Clean Architecture in .NET Explained (The Dependency Rule)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Mon, 25 May 2026 05:23:10 +0000</pubDate>
      <link>https://forem.com/kirandeepjassalcrypto/clean-architecture-in-net-explained-the-dependency-rule-gn9</link>
      <guid>https://forem.com/kirandeepjassalcrypto/clean-architecture-in-net-explained-the-dependency-rule-gn9</guid>
      <description>&lt;p&gt;If you've ever upgraded EF Core and had to touch 300 files, or tried to unit-test a single business rule and realized you needed a running database first — you've already felt the problem Clean Architecture solves.&lt;/p&gt;

&lt;p&gt;This is the short version. There's a full deep-dive (four layers, real .NET 8 code with EF Core + MediatR, the honest trade-offs, and the pragmatic 2-project version most teams actually ship) linked at the bottom, plus a video walkthrough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule
&lt;/h2&gt;

&lt;p&gt;Clean Architecture has exactly one rule worth memorizing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source code dependencies can only point inward.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Picture concentric circles. Your business logic sits in the center and references nothing. The framework, the database, and the UI live on the outer rings and depend on the center — never the other way around. Invert the dependencies and everything else falls into place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four layers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Domain&lt;/strong&gt; — entities, value objects, and pure business rules. Zero framework code: no &lt;code&gt;[Table]&lt;/code&gt;, no &lt;code&gt;[Key]&lt;/code&gt;, no EF Core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application&lt;/strong&gt; — use cases (command/query handlers). It &lt;em&gt;defines the interfaces&lt;/em&gt; it needs, like &lt;code&gt;IOrderRepository&lt;/code&gt; and &lt;code&gt;IClock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure&lt;/strong&gt; — the implementations: EF Core, HTTP clients, email, file system. It implements the interfaces Application declared.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presentation (Web/API)&lt;/strong&gt; — thin controllers that validate input, dispatch to a use case, and format the response. No business logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The magic is the inversion: Application says &lt;em&gt;what&lt;/em&gt; it needs, Infrastructure provides &lt;em&gt;how&lt;/em&gt;, and the DI container wires them together at startup. The use case never references EF Core, so you can test it with a simple fake.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlaceOrderHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRequestHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlaceOrderCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IOrderRepository&lt;/span&gt; &lt;span class="n"&gt;_orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IClock&lt;/span&gt; &lt;span class="n"&gt;_clock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrderHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOrderRepository&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IClock&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_clock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PlaceOrderCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Place&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;MapItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;_clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;DateTime.UtcNow&lt;/code&gt;, no &lt;code&gt;HttpContext&lt;/code&gt;, no database. That handler is testable in milliseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforced by the compiler
&lt;/h2&gt;

&lt;p&gt;Split the solution into projects and the Dependency Rule enforces itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Domain.csproj&lt;/code&gt; -&amp;gt; no references&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Application.csproj&lt;/code&gt; -&amp;gt; references Domain&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Infrastructure.csproj&lt;/code&gt; -&amp;gt; references Application + Domain&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Web.csproj&lt;/code&gt; -&amp;gt; references everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If someone tries to &lt;code&gt;using Microsoft.EntityFrameworkCore&lt;/code&gt; inside the Application project, the build fails. Architecture enforced by the compiler, not by code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use it
&lt;/h2&gt;

&lt;p&gt;It isn't free — more files, more indirection, a steeper onboarding curve. Skip it for CRUD admin tools, MVPs, and short-lived projects. Reach for it when you have real business rules, a team of 3+, and a multi-year lifetime.&lt;/p&gt;




&lt;p&gt;📺 &lt;strong&gt;Video walkthrough:&lt;/strong&gt; &lt;a href="https://www.youtube.com/channel/UCop7DtrfDIzEgyLbQxKxB7g" rel="noopener noreferrer"&gt;https://www.youtube.com/channel/UCop7DtrfDIzEgyLbQxKxB7g&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📖 &lt;strong&gt;Full guide — complete .NET 8 code, the four layers in depth, common pitfalls, and the pragmatic version:&lt;/strong&gt; &lt;a href="https://prepstack.co.in/blog/clean-architecture-csharp-complete-guide" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/clean-architecture-csharp-complete-guide&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>ASP.NET Web API — A Complete Guide from Basics to Advanced</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Sun, 24 May 2026 11:23:16 +0000</pubDate>
      <link>https://forem.com/kirandeepjassalcrypto/aspnet-web-api-a-complete-guide-from-basics-to-advanced-5ebc</link>
      <guid>https://forem.com/kirandeepjassalcrypto/aspnet-web-api-a-complete-guide-from-basics-to-advanced-5ebc</guid>
      <description>&lt;p&gt;REST principles, architecture, real .NET code with controllers and minimal APIs, JWT auth, versioning, and where Web API fits in 2026.&lt;br&gt;
tags: dotnet, webapi, csharp, beginners&lt;br&gt;
canonical_url: &lt;a href="https://prepstack.co.in/blog/aspnet-web-api-complete-guide-basics-to-advanced" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/aspnet-web-api-complete-guide-basics-to-advanced&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  cover_image: &lt;a href="https://prepstack.co.in/opengraph-image" rel="noopener noreferrer"&gt;https://prepstack.co.in/opengraph-image&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://prepstack.co.in/blog/aspnet-web-api-complete-guide-basics-to-advanced" rel="noopener noreferrer"&gt;PrepStack&lt;/a&gt; — full deep-dive with all code examples + architecture diagrams below ↓&lt;br&gt;
ASP.NET Web API is the framework you use when your client is not a browser asking for HTML — it is a mobile app, a single-page application, a partner integration, or another microservice asking for JSON. It is the same routing + DI + middleware as MVC, but the result is data, not a rendered page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This guide is the complete picture: REST principles, architecture, minimal vs controller-based APIs, auth, versioning, performance, and where Web API fits in 2026.&lt;/p&gt;

&lt;p&gt;Why Web API exists&lt;/p&gt;

&lt;p&gt;Web pages were the entire web 15 years ago. Now most software is a backend talking to many clients: an iOS app, an Android app, a React SPA, partner B2B calls, internal cron jobs, third-party webhooks. They all need a stable, language-agnostic way to call the server.&lt;/p&gt;

&lt;p&gt;The de-facto standard is HTTP + JSON with REST conventions. Web API is the toolkit for building exactly that on .NET, with everything ASP.NET already does well: routing, model binding, DI, filters, validation, identity.&lt;/p&gt;

&lt;p&gt;The architecture in one diagram&lt;/p&gt;

&lt;p&gt;Mobile app           SPA (React, Vue)        Partner system&lt;br&gt;
        │                       │                       │&lt;br&gt;
        └──────────┬────────────┴──────────┬────────────┘&lt;br&gt;
                   │                       │&lt;br&gt;
                   ▼                       ▼&lt;br&gt;
              ┌──────────────────────────────────┐&lt;br&gt;
              │      Reverse Proxy / Gateway     │  TLS, rate limit, WAF&lt;br&gt;
              │   (NGINX / YARP / Azure Front)   │&lt;br&gt;
              └──────────────────┬───────────────┘&lt;br&gt;
                                 │&lt;br&gt;
                                 ▼&lt;br&gt;
        ┌────────────────────────────────────────────────┐&lt;br&gt;
        │              ASP.NET Web API Host              │&lt;br&gt;
        │                                                │&lt;br&gt;
        │  ┌──────────────────────────────────────────┐  │&lt;br&gt;
        │  │              Middleware                  │  │&lt;br&gt;
        │  │  Auth → CORS → RateLimit → Exception →   │  │&lt;br&gt;
        │  └──────────────────┬───────────────────────┘  │&lt;br&gt;
        │                     │                          │&lt;br&gt;
        │                     ▼                          │&lt;br&gt;
        │  ┌──────────────────────────────────────────┐  │&lt;br&gt;
        │  │       Routing + Model Binding            │  │&lt;br&gt;
        │  └──────────────────┬───────────────────────┘  │&lt;br&gt;
        │                     │                          │&lt;br&gt;
        │                     ▼                          │&lt;br&gt;
        │  ┌──────────────────────────────────────────┐  │&lt;br&gt;
        │  │       Controller / Endpoint              │  │&lt;br&gt;
        │  │   public async Task GetAsync()   │  │&lt;br&gt;
        │  └──────────────────┬───────────────────────┘  │&lt;br&gt;
        │                     │                          │&lt;br&gt;
        │                     ▼                          │&lt;br&gt;
        │  ┌──────────────────────────────────────────┐  │&lt;br&gt;
        │  │   Services + DbContext + Cache + HTTP    │  │&lt;br&gt;
        │  └──────────────────┬───────────────────────┘  │&lt;br&gt;
        │                     │                          │&lt;br&gt;
        │                     ▼                          │&lt;br&gt;
        │     JSON serializer → HTTP response            │&lt;br&gt;
        └────────────────────────────────────────────────┘&lt;br&gt;
                                 │&lt;br&gt;
                                 ▼&lt;br&gt;
                        ┌──────────────────┐&lt;br&gt;
                        │   PostgreSQL     │&lt;br&gt;
                        │   Redis          │&lt;br&gt;
                        │   Other APIs     │&lt;br&gt;
                        └──────────────────┘&lt;/p&gt;

&lt;p&gt;Notice three things absent from this picture: there is no view, no Razor, no HTML. Everything is JSON in / JSON out.&lt;/p&gt;

&lt;p&gt;REST in 60 seconds&lt;/p&gt;

&lt;p&gt;REST is a set of conventions, not a strict standard:&lt;/p&gt;

&lt;p&gt;HTTP verb&lt;/p&gt;

&lt;p&gt;Means&lt;/p&gt;

&lt;p&gt;Returns&lt;/p&gt;

&lt;p&gt;GET&lt;/p&gt;

&lt;p&gt;Read, idempotent, safe&lt;/p&gt;

&lt;p&gt;200 OK, 404 Not Found&lt;/p&gt;

&lt;p&gt;POST&lt;/p&gt;

&lt;p&gt;Create&lt;/p&gt;

&lt;p&gt;201 Created + Location header&lt;/p&gt;

&lt;p&gt;PUT&lt;/p&gt;

&lt;p&gt;Replace whole resource&lt;/p&gt;

&lt;p&gt;200 / 204 No Content&lt;/p&gt;

&lt;p&gt;PATCH&lt;/p&gt;

&lt;p&gt;Modify part of a resource&lt;/p&gt;

&lt;p&gt;200 / 204&lt;/p&gt;

&lt;p&gt;DELETE&lt;/p&gt;

&lt;p&gt;Remove&lt;/p&gt;

&lt;p&gt;204 No Content / 404&lt;/p&gt;

&lt;p&gt;URLs name resources, not actions:&lt;/p&gt;

&lt;p&gt;GET    /orders             list orders&lt;br&gt;
GET    /orders/123         get one order&lt;br&gt;
POST   /orders             create&lt;br&gt;
PUT    /orders/123         replace&lt;br&gt;
PATCH  /orders/123         partial update&lt;br&gt;
DELETE /orders/123         remove&lt;/p&gt;

&lt;p&gt;GET    /orders/123/items   nested resource&lt;/p&gt;

&lt;p&gt;Status codes tell the client what happened without parsing the body:&lt;/p&gt;

&lt;p&gt;200 OK            success&lt;br&gt;
201 Created       new resource at Location: header&lt;br&gt;
204 No Content    success, no body&lt;br&gt;
400 Bad Request   client-side validation error&lt;br&gt;
401 Unauthorized  missing / invalid auth&lt;br&gt;
403 Forbidden     authenticated but not allowed&lt;br&gt;
404 Not Found     resource missing&lt;br&gt;
409 Conflict      version mismatch / duplicate&lt;br&gt;
422 Unprocessable validation error on otherwise-valid request&lt;br&gt;
500 Server Error  bug, log + alert&lt;/p&gt;

&lt;p&gt;A complete minimal Web API&lt;/p&gt;

&lt;p&gt;Project bootstrap&lt;/p&gt;

&lt;p&gt;var builder = WebApplication.CreateBuilder(args);&lt;br&gt;
builder.Services.AddDbContext(o =&amp;gt; o.UseNpgsql(builder.Configuration.GetConnectionString("Db")!));&lt;br&gt;
builder.Services.AddScoped();&lt;br&gt;
builder.Services.AddControllers();&lt;br&gt;
builder.Services.AddEndpointsApiExplorer();&lt;br&gt;
builder.Services.AddOpenApi();   // .NET 9+&lt;br&gt;
var app = builder.Build();&lt;/p&gt;

&lt;p&gt;app.UseExceptionHandler("/error");&lt;br&gt;
app.MapControllers();&lt;br&gt;
app.MapOpenApi();&lt;br&gt;
app.Run();&lt;/p&gt;

&lt;p&gt;Controller-based API&lt;/p&gt;

&lt;p&gt;[ApiController]&lt;br&gt;
[Route("orders")]&lt;br&gt;
public class OrdersController : ControllerBase&lt;br&gt;
{&lt;br&gt;
    private readonly OrderService _service;&lt;br&gt;
    public OrdersController(OrderService service) =&amp;gt; _service = service;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[HttpGet]
public Task&amp;lt;IEnumerable&amp;lt;OrderDto&amp;gt;&amp;gt; List([FromQuery] int page = 1) =&amp;gt;
    _service.ListAsync(page);

[HttpGet("{id:guid}")]
public async Task&amp;lt;ActionResult&amp;lt;OrderDto&amp;gt;&amp;gt; Get(Guid id)
{
    var order = await _service.GetAsync(id);
    return order is null ? NotFound() : Ok(order);
}

[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task&amp;lt;ActionResult&amp;lt;OrderDto&amp;gt;&amp;gt; Create(CreateOrderRequest req)
{
    var created = await _service.CreateAsync(req);
    return CreatedAtAction(nameof(Get), new { id = created.Id }, created);
}

[HttpDelete("{id:guid}")]
public async Task&amp;lt;IActionResult&amp;gt; Delete(Guid id)
{
    await _service.DeleteAsync(id);
    return NoContent();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;[ApiController] gives you:&lt;/p&gt;

&lt;p&gt;Automatic 400 on invalid model state&lt;/p&gt;

&lt;p&gt;Binding source inference (route, query, body)&lt;/p&gt;

&lt;p&gt;Standardized error responses&lt;/p&gt;

&lt;p&gt;Minimal API — same thing, fewer lines&lt;/p&gt;

&lt;p&gt;var orders = app.MapGroup("/orders").WithTags("Orders");&lt;/p&gt;

&lt;p&gt;orders.MapGet("/", (OrderService svc, int page = 1) =&amp;gt; svc.ListAsync(page));&lt;/p&gt;

&lt;p&gt;orders.MapGet("/{id:guid}", async (Guid id, OrderService svc) =&amp;gt;&lt;br&gt;
    await svc.GetAsync(id) is { } o ? Results.Ok(o) : Results.NotFound());&lt;/p&gt;

&lt;p&gt;orders.MapPost("/", async (CreateOrderRequest req, OrderService svc) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    var created = await svc.CreateAsync(req);&lt;br&gt;
    return Results.CreatedAtRoute("GetOrder", new { id = created.Id }, created);&lt;br&gt;
}).Accepts("application/json");&lt;/p&gt;

&lt;p&gt;orders.MapDelete("/{id:guid}", async (Guid id, OrderService svc) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    await svc.DeleteAsync(id);&lt;br&gt;
    return Results.NoContent();&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;Minimal APIs are shorter for simple endpoints. Controllers are clearer for big projects with cross-cutting filters.&lt;/p&gt;

&lt;p&gt;Request and response DTOs&lt;/p&gt;

&lt;p&gt;Never expose your DB entities directly. Use DTOs (data transfer objects) — input and output shapes you control.&lt;/p&gt;

&lt;p&gt;public record CreateOrderRequest(&lt;br&gt;
    [Required] string CustomerEmail,&lt;br&gt;
    [Required, MinLength(1)] List Items);&lt;/p&gt;

&lt;p&gt;public record LineItem([Required] Guid ProductId, [Range(1, 999)] int Quantity);&lt;/p&gt;

&lt;p&gt;public record OrderDto(Guid Id, string Status, decimal Total, DateTimeOffset CreatedAt);&lt;/p&gt;

&lt;p&gt;Why DTOs:&lt;/p&gt;

&lt;p&gt;API surface is stable even if the DB schema evolves&lt;/p&gt;

&lt;p&gt;Avoids leaking sensitive fields (password_hash, internal_notes)&lt;/p&gt;

&lt;p&gt;Lets validation rules live next to the input shape&lt;/p&gt;

&lt;p&gt;Intermediate — versioning, error handling, OpenAPI&lt;/p&gt;

&lt;p&gt;Versioning&lt;/p&gt;

&lt;p&gt;builder.Services.AddApiVersioning(o =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    o.DefaultApiVersion = new ApiVersion(1, 0);&lt;br&gt;
    o.AssumeDefaultVersionWhenUnspecified = true;&lt;br&gt;
    o.ReportApiVersions = true;&lt;br&gt;
    o.ApiVersionReader = ApiVersionReader.Combine(&lt;br&gt;
        new UrlSegmentApiVersionReader(),&lt;br&gt;
        new HeaderApiVersionReader("X-Api-Version"));&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;[ApiController]&lt;br&gt;
[Route("v{version:apiVersion}/orders")]&lt;br&gt;
[ApiVersion("1.0")]&lt;br&gt;
[ApiVersion("2.0")]&lt;br&gt;
public class OrdersController : ControllerBase&lt;br&gt;
{&lt;br&gt;
    [HttpGet, MapToApiVersion("1.0")]&lt;br&gt;
    public IEnumerable ListV1() { ... }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[HttpGet, MapToApiVersion("2.0")]
public IEnumerable&amp;lt;OrderDtoV2&amp;gt; ListV2() { ... }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;URL versioning (/v1/orders) is the most discoverable; header versioning (X-Api-Version: 2.0) keeps URLs stable.&lt;/p&gt;

&lt;p&gt;Centralized error handling — RFC 7807&lt;/p&gt;

&lt;p&gt;app.UseExceptionHandler(handler =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    handler.Run(async ctx =&amp;gt;&lt;br&gt;
    {&lt;br&gt;
        var ex = ctx.Features.Get()?.Error;&lt;br&gt;
        var problem = ex switch&lt;br&gt;
        {&lt;br&gt;
            ValidationException v =&amp;gt; new ProblemDetails {&lt;br&gt;
                Status = 400, Title = "Validation failed", Detail = v.Message&lt;br&gt;
            },&lt;br&gt;
            NotFoundException     =&amp;gt; new ProblemDetails { Status = 404, Title = "Not found" },&lt;br&gt;
            _                     =&amp;gt; new ProblemDetails { Status = 500, Title = "Server error" },&lt;br&gt;
        };&lt;br&gt;
        ctx.Response.StatusCode = problem.Status ?? 500;&lt;br&gt;
        await ctx.Response.WriteAsJsonAsync(problem);&lt;br&gt;
    });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;Clients now receive a consistent JSON error envelope they can parse and display.&lt;/p&gt;

&lt;p&gt;OpenAPI / Swagger&lt;/p&gt;

&lt;p&gt;AddOpenApi() (.NET 9+) auto-generates a machine-readable spec at /openapi/v1.json. From it you get:&lt;/p&gt;

&lt;p&gt;Swagger UI for humans to try the API&lt;/p&gt;

&lt;p&gt;Auto-generated client SDKs (NSwag, OpenAPI Generator)&lt;/p&gt;

&lt;p&gt;Postman collection import&lt;/p&gt;

&lt;p&gt;This is non-optional in a real product — every Web API should ship its OpenAPI spec.&lt;/p&gt;

&lt;p&gt;Advanced — auth, rate limiting, output caching&lt;/p&gt;

&lt;p&gt;Authentication with JWT&lt;/p&gt;

&lt;p&gt;builder.Services&lt;br&gt;
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)&lt;br&gt;
    .AddJwtBearer(opts =&amp;gt;&lt;br&gt;
    {&lt;br&gt;
        opts.TokenValidationParameters = new()&lt;br&gt;
        {&lt;br&gt;
            ValidateIssuer = true,&lt;br&gt;
            ValidateAudience = true,&lt;br&gt;
            ValidateLifetime = true,&lt;br&gt;
            ValidateIssuerSigningKey = true,&lt;br&gt;
            ValidIssuer = cfg["Jwt:Issuer"],&lt;br&gt;
            ValidAudience = cfg["Jwt:Audience"],&lt;br&gt;
            IssuerSigningKey = new SymmetricSecurityKey(&lt;br&gt;
                Encoding.UTF8.GetBytes(cfg["Jwt:Key"]!)),&lt;br&gt;
        };&lt;br&gt;
    });&lt;/p&gt;

&lt;p&gt;builder.Services.AddAuthorization();&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;[Authorize]&lt;br&gt;
[HttpGet("me")]&lt;br&gt;
public Task Me() =&amp;gt; _users.MeAsync(User);&lt;/p&gt;

&lt;p&gt;[Authorize(Roles = "admin")]&lt;br&gt;
[HttpDelete("orders/{id:guid}")]&lt;br&gt;
public Task Delete(Guid id) =&amp;gt; _orders.DeleteAsync(id);&lt;/p&gt;

&lt;p&gt;Rate limiting (.NET 7+)&lt;/p&gt;

&lt;p&gt;builder.Services.AddRateLimiter(o =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    o.AddFixedWindowLimiter("api", opt =&amp;gt;&lt;br&gt;
    {&lt;br&gt;
        opt.PermitLimit = 100;&lt;br&gt;
        opt.Window = TimeSpan.FromMinutes(1);&lt;br&gt;
    });&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;app.UseRateLimiter();&lt;/p&gt;

&lt;p&gt;[EnableRateLimiting("api")]&lt;br&gt;
public class PublicController : ControllerBase { ... }&lt;/p&gt;

&lt;p&gt;Output caching (.NET 7+)&lt;/p&gt;

&lt;p&gt;builder.Services.AddOutputCache(o =&amp;gt; o.AddPolicy("Products", b =&amp;gt; b.Expire(TimeSpan.FromMinutes(5))));&lt;br&gt;
app.UseOutputCache();&lt;/p&gt;

&lt;p&gt;[OutputCache(PolicyName = "Products")]&lt;br&gt;
[HttpGet("products")]&lt;br&gt;
public Task&amp;gt; List() =&amp;gt; _service.ListAsync();&lt;/p&gt;

&lt;p&gt;CORS — the SPA gotcha&lt;/p&gt;

&lt;p&gt;builder.Services.AddCors(o =&amp;gt; o.AddPolicy("spa", p =&amp;gt;&lt;br&gt;
    p.WithOrigins("&lt;a href="https://app.example.com%22" rel="noopener noreferrer"&gt;https://app.example.com"&lt;/a&gt;)&lt;br&gt;
     .AllowAnyHeader().AllowAnyMethod().AllowCredentials()));&lt;br&gt;
app.UseCors("spa");&lt;/p&gt;

&lt;p&gt;A React SPA on a different origin cannot call your API without this. Pinning origin is critical — AllowAnyOrigin() is fine for public read-only APIs, dangerous for anything else.&lt;/p&gt;

&lt;p&gt;Advantages of ASP.NET Web API&lt;/p&gt;

&lt;p&gt;Native to .NET — same DI, same logging, same testing tools you already use&lt;/p&gt;

&lt;p&gt;Async throughput — Kestrel + async controllers handle thousands of concurrent requests per core&lt;/p&gt;

&lt;p&gt;OpenAPI built-in — clients in any language can be generated&lt;/p&gt;

&lt;p&gt;Strong typing — DTOs catch breaking changes at compile time, not 3 AM in prod&lt;/p&gt;

&lt;p&gt;Minimal API option for small services keeps the file count low&lt;/p&gt;

&lt;p&gt;First-class JWT, OAuth, identity, rate-limiting, output cache — no third-party glue needed&lt;/p&gt;

&lt;p&gt;Cross-platform — Linux containers, Windows servers, macOS dev machines, all identical&lt;/p&gt;

&lt;p&gt;Disadvantages — when Web API is the wrong tool&lt;/p&gt;

&lt;p&gt;JSON overhead — text-based, larger than binary protocols. For internal service-to-service, gRPC saves bandwidth and parsing cost.&lt;/p&gt;

&lt;p&gt;No SSR / SEO — search engines see nothing useful in JSON. You need MVC, Blazor, or a SPA with SSR for content sites.&lt;/p&gt;

&lt;p&gt;REST is conventions, not enforced — teams disagree about PUT vs PATCH, nesting, error shapes. Add an API style guide.&lt;/p&gt;

&lt;p&gt;Stateful protocols (chat, telemetry) — SignalR / WebSockets are better than polling REST.&lt;/p&gt;

&lt;p&gt;Versioning is real work — every breaking change forces a v2; old clients linger for years.&lt;/p&gt;

&lt;p&gt;REST vs gRPC vs GraphQL — quick guide&lt;/p&gt;

&lt;p&gt;Use REST when&lt;/p&gt;

&lt;p&gt;Use gRPC when&lt;/p&gt;

&lt;p&gt;Use GraphQL when&lt;/p&gt;

&lt;p&gt;Public API for any HTTP client&lt;/p&gt;

&lt;p&gt;Internal service-to-service traffic&lt;/p&gt;

&lt;p&gt;UI fetches deeply nested data with many shapes&lt;/p&gt;

&lt;p&gt;Browsers and curl call it directly&lt;/p&gt;

&lt;p&gt;Strong typing + small payloads matter&lt;/p&gt;

&lt;p&gt;Front-end teams iterate fast, back-end ships slowly&lt;/p&gt;

&lt;p&gt;Wide partner ecosystem&lt;/p&gt;

&lt;p&gt;Streaming bidirectionally&lt;/p&gt;

&lt;p&gt;Multiple clients (mobile, web, partner) want different fields&lt;/p&gt;

&lt;p&gt;Caching via HTTP semantics&lt;/p&gt;

&lt;p&gt;Polyglot internal stack&lt;/p&gt;

&lt;p&gt;Aggregator over microservices&lt;/p&gt;

&lt;p&gt;You can have all three in one organization. Most Indian product teams I have worked with pick: REST for the public API, gRPC for service-to-service, GraphQL only when the UI variability justifies the operational cost.&lt;/p&gt;

&lt;p&gt;Where Web API fits in 2026&lt;/p&gt;

&lt;p&gt;React / Vue / Angular SPAs talking to a .NET backend&lt;/p&gt;

&lt;p&gt;iOS and Android apps calling a single REST service&lt;/p&gt;

&lt;p&gt;Partner integrations (webhooks, B2B data sync)&lt;/p&gt;

&lt;p&gt;Internal admin tools whose UI lives in React&lt;/p&gt;

&lt;p&gt;Backend-for-frontend (BFF) services in front of microservices&lt;/p&gt;

&lt;p&gt;Background workers + admin API on the same host&lt;/p&gt;

&lt;p&gt;Where Web API does NOT fit&lt;/p&gt;

&lt;p&gt;High-frequency internal traffic — gRPC wins on bandwidth + latency&lt;/p&gt;

&lt;p&gt;Browser-rendered HTML — use MVC or Blazor Server&lt;/p&gt;

&lt;p&gt;Real-time push (typing indicators, live cursors) — SignalR / WebSockets&lt;/p&gt;

&lt;p&gt;Production checklist&lt;/p&gt;

&lt;p&gt;Health checks — MapHealthChecks("/health") separately for liveness and readiness&lt;/p&gt;

&lt;p&gt;Structured logging — Serilog with correlation IDs, never Console.WriteLine&lt;/p&gt;

&lt;p&gt;Retries on transient errors — Polly or IHttpClientFactory.AddStandardResilienceHandler()&lt;/p&gt;

&lt;p&gt;Idempotency-Key header on POST endpoints clients might retry&lt;/p&gt;

&lt;p&gt;OpenAPI shipped to /openapi/v1.json even on prod (or behind auth) so clients can regenerate&lt;/p&gt;

&lt;p&gt;Pagination by cursor on large list endpoints, not offset&lt;/p&gt;

&lt;p&gt;Filter validation — never accept arbitrary ?orderBy= strings → SQL injection risk&lt;/p&gt;

&lt;p&gt;Don't return entire DB rows — always DTO&lt;/p&gt;

&lt;p&gt;Async all the way — no .Result, no .Wait()&lt;/p&gt;

&lt;p&gt;HTTPS only — app.UseHttpsRedirection()&lt;/p&gt;

&lt;p&gt;Migration paths&lt;/p&gt;

&lt;p&gt;From WCF / SOAP → REST: pick one resource model, expose JSON shapes, deprecate SOAP over 12 months while clients migrate.&lt;/p&gt;

&lt;p&gt;From REST → gRPC: keep REST as the public face; introduce gRPC for service-to-service. Most organizations end up with both.&lt;/p&gt;

&lt;p&gt;From .NET Framework Web API → ASP.NET Core: API controllers map almost one-to-one. The biggest changes are DI registration, configuration, and HttpContext.&lt;/p&gt;

&lt;p&gt;Summary&lt;/p&gt;

&lt;p&gt;ASP.NET Web API is the right choice when clients need data, not pages. It is the .NET answer to "give me a fast, strongly-typed, cross-platform JSON API with everything I need to ship a real product — auth, validation, caching, rate-limiting, OpenAPI."&lt;/p&gt;

&lt;p&gt;Start with [ApiController] and controller classes. Add DTOs, validation, JWT auth, versioning, error handling, output cache as the surface grows. Reach for minimal APIs when a service is tiny. Reach for gRPC when service-to-service throughput justifies the schema discipline. Layer a gateway (YARP, Azure Front Door) in front when you have more than one service.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
