<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>YogiHosting</title>
	<atom:link href="https://www.yogihosting.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.yogihosting.com/</link>
	<description>Tutorials on ASP.NET Core, Blazor, jQuery, JavaScript, Entity Framework, Identity, WordPress, SQL, HTML &#38; more</description>
	<lastBuildDate>Tue, 15 Sep 2026 03:24:19 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>
	<item>
		<title>ASP.NET Core Interview Questions and Answers – Crack Your Next Interview</title>
		<link>https://www.yogihosting.com/aspnet-core-interview-questions/</link>
					<comments>https://www.yogihosting.com/aspnet-core-interview-questions/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Tue, 15 Sep 2026 03:24:17 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=23683</guid>

					<description><![CDATA[<p>Cracking an ASP.NET Core interview is not just about memorizing a list of interview questions—it is about understanding how the framework works and being able to apply that knowledge to real-world development scenarios. Interviewers often test your understanding of core concepts such as Dependency Injection, middleware, routing, Web APIs, Entity Framework Core, authentication and authorization, [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-interview-questions/">ASP.NET Core Interview Questions and Answers – Crack Your Next Interview</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Cracking an ASP.NET Core interview is not just about memorizing a list of interview questions—it is about understanding how the framework works and being able to apply that knowledge to real-world development scenarios. Interviewers often test your understanding of core concepts such as Dependency Injection, middleware, routing, Web APIs, Entity Framework Core, authentication and authorization, performance optimization, and asynchronous programming, along with your ability to solve practical problems. In this guide, we will cover the most important ASP.NET Core interview questions and answers, from beginner-level fundamentals to advanced and scenario-based questions, so you can build confidence and prepare effectively for your next ASP.NET Core interview.</p>



<span id="more-23683"></span>



<p>Absolutely. Here is a practical ASP.NET Core interview question set, organized from beginner to advanced. It covers topics commonly asked for ASP.NET Core Web API, MVC, Minimal APIs, Entity Framework Core, authentication/authorization, dependency injection, middleware, performance, and .NET 10.</p>
<div class="starBlock">I have compiled 100 of the best and most popular ASP.NET Core interview questions covering the concepts, features, and real-world scenarios most frequently discussed in interviews. These questions are carefully selected to help you focus your preparation on the topics that matter most. I am confident that at least 80–85% of the questions you encounter in an ASP.NET Core interview will be related to the concepts covered in these 100 questions. Prepare these questions thoroughly, understand the concepts behind them, and you will be in a strong position to confidently face your ASP.NET Core interview.</div>
<h2>ASP.NET Core Basics Interview Questions</h2>
<p>ASP.NET Core basics questions are usually the first step in an interview because they help the interviewer understand how strong your fundamental knowledge of the framework is. Questions about <b>Program.cs, middleware, the request pipeline, dependency injection, configuration, hosting, routing, and environments</b> may seem simple, but your answers reveal whether you understand how an ASP.NET Core application actually works behind the scenes. These questions also help the interviewer judge your technical foundation, clarity of concepts, practical experience, and ability to explain technical topics, rather than simply checking whether you have memorized definitions. A strong understanding of these fundamentals will also make it much easier to answer advanced ASP.NET Core questions later in the interview.</p>
<p><b>1. What is ASP.NET Core and it&#8217;s advantages over ASP.NET Framework? Also explain difference between .NET Core, and modern .NET?</b></p>
<p>ASP.NET Core is a cross-platform, open-source, high-performance framework from Microsoft for building modern web applications, RESTful Web APIs, Minimal APIs, MVC applications, real-time applications, and other server-side web solutions. It runs on Windows, Linux, and macOS and is part of the modern unified .NET platform.</p>
<p>ASP.NET Core was designed as a successor to the older ASP.NET Framework, addressing several of its limitations while providing better performance, flexibility, scalability, and support for modern application development.</p>
<div class="note">Advantages of ASP.NET Core over ASP.NET Framework</div>
<p>The biggest difference is that ASP.NET Core is a modern, cross-platform, high-performance framework designed for today&#8217;s cloud, container, API, and web application development, whereas ASP.NET Framework is the older Windows-focused web framework.</p>
<p>For new applications, ASP.NET Core is generally the preferred choice, while ASP.NET Framework remains relevant when maintaining existing legacy applications or applications that depend on technologies available only in the .NET Framework.</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>ASP.NET Core</th>
<th>ASP.NET Framework</th>
</tr>
</thead>
<tbody>
<tr>
<td><b>Cross-platform</b> — runs on Windows, Linux, and macOS</td>
<td>Primarily designed for Windows</td>
 </tr>
<tr>
<td><b>High performance</b> and optimized for modern workloads</td>
<td>Generally lower performance for modern web workloads</td>
</tr>
<tr>
<td><b>Open source</b></td>
<td>Parts of the framework are open source, but traditionally Windows-focused</td>
</tr>
<tr>
<td>Supports modern <strong>.NET versions</strong></td>
<td>Based on the older .NET Framework</td>
</tr>
<tr>
<td>Built-in <strong>Dependency Injection</strong></td>
<td>DI is not built into the framework in the same way</td>
</tr>
<tr>
<td>Lightweight and modular</td>
<td>Larger, more monolithic framework</td>
</tr>
<tr>
<td>Excellent support for <strong>Web APIs and Minimal APIs</strong></td>
<td>Primarily uses Web API, MVC, Web Forms, etc.</td>
</tr>
<tr>
<td>Designed for <strong>cloud-native applications</strong></td>
<td>Not originally designed around cloud-native development</td>
</tr>
<tr>
<td>Runs well with <strong>Docker and containers</strong></td>
<td>Container support is more limited and Windows-oriented</td>
</tr>
<tr>
<td>Can be hosted using <strong>Kestrel</strong> and behind reverse proxies</td>
<td>Commonly hosted with IIS</td>
</tr>
<tr>
<td>Supports modern middleware-based request pipelines</td>
<td>Uses older HTTP/application pipeline models</td>
</tr>
<tr>
<td>Actively developed as part of modern .NET</td>
<td>.NET Framework is largely in maintenance mode</td>
</tr>
</tbody>
</table>
</div>
<div class="note">Difference between .NET Core, and modern .NET?</div>
<p>.NET Core was the name used for Microsoft&#8217;s cross-platform .NET platform from versions 1.0 through 3.1. Starting with .NET 5, Microsoft unified the platform and dropped the &#8220;Core&#8221; name. Therefore, .NET 5 and later—including .NET 10—are simply called .NET. ASP.NET Core, however, retained the &#8220;Core&#8221; name. So today, you should generally say &#8220;.NET 10&#8221;, not &#8220;.NET Core 10&#8221;.</p>
<p><b>2. What is Program.cs in ASP.NET Core, WebApplication, and explain the purpose of WebApplicationBuilder ?</b></p>
<p><span class="term">Program.cs</span> is the entry point of an ASP.NET Core application. Since .NET 6, it uses a minimal hosting model — meaning all the setup that used to be spread across <span class="term">Program.cs</span> (with <span class="term">ConfigureServices</span> and <span class="term">Configure</span> methods) is now consolidated into a single file with top-level statements (no <span class="term">Main</span> method or class boilerplate required).</p>
<p>A typical modern <span class="term">Program.cs</span> looks like this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var builder = WebApplication.CreateBuilder(args);

// Register services (dependency injection container)
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext&lt;AppDbContext&gt;(options =&gt;
    options.UseSqlServer(builder.Configuration.GetConnectionString(&quot;Default&quot;)));

var app = builder.Build();

// Configure the HTTP request pipeline (middleware)
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();
</pre></div>


<p>It handles three jobs in sequence: build configuration and register services, build the app, then configure the middleware pipeline and start listening for requests.</p>
<h3>What is WebApplication ?</h3>
<p>WebApplication is the object you get back after calling <span class="code">builder.Build()</span> on a WebApplicationBuilder. It represents your fully configured, ready-to-run application, and it&#8217;s what you use to define the HTTP request pipeline and start the server.</p>



<pre class="wp-block-code"><code>var builder = WebApplication.CreateBuilder(args);
// ... register services on builder.Services ...

var app = builder.Build(); // &lt;-- this is the WebApplication

// From here on, you configure app, not builder
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();</code></pre>



<p>WebApplication implements several interfaces at once, which is why it can do so much with one object:</p>
<ol>
<li><b>IHost</b> — it&#8217;s the application host, so it manages the app&#8217;s lifetime (starting, running, stopping, graceful shutdown).</li>
<li><b>IApplicationBuilder</b> — this is what lets you build the middleware pipeline using <span class="code">app.Use&#8230;()</span> methods (UseRouting, UseAuthentication, UseHttpsRedirection, custom middleware via <span class="code">app.Use(&#8230;)</span>, etc.). Middleware runs in the order you add it, for every incoming request.</li>
<li><b>IEndpointRouteBuilder</b> — this is what lets you map endpoints directly on it: <span class="code">app.MapGet(&#8230;)</span>, <span class="code">app.MapControllers()</span>, <span class="code">app.MapRazorPages()</span>, <span class="code">app.MapHub&lt;t>()</span>, etc.</li>
</ol>
<p>What you typically do with it:</p>
<ol>
<li>Read environment info — <span class="code">app.Environment.IsDevelopment()</span>, <span class="code">IsProduction()</span>, etc., to conditionally add middleware.</li>
<li>Configure the middleware pipeline.</li>
<li>Map endpoints — minimal APIs directly, or MVC/Razor Pages routes.</li>
<li>Access services — via <span class="code">app.Services</span> (an IServiceProvider), useful for things like running startup logic or seeding a database.</li>
<li>Run the app — <span class="code">app.Run()</span> starts listening for requests and blocks until shutdown. There&#8217;s also <span class="code">app.RunAsync()</span> and <span class="code">app.Start()</span>/<span class="code">app.StopAsync()</span> for more control.</li>
</ol>
<h3>Purpose of WebApplicationBuilder</h3>
<p><span class="term">WebApplicationBuilder</span> (created via <span class="code">WebApplication.CreateBuilder(args)</span>) is the object responsible for assembling everything the app needs before it starts running. It bundles together several things that used to be configured separately:</p>
<ol>
<li><b>Configuration</b> — automatically loads settings from appsettings.json, appsettings.{Environment}.json, environment variables, command-line args, and user secrets, all merged into builder.Configuration.</li>
<li><b>Dependency Injection container</b> — builder.Services is an IServiceCollection where you register your services, repositories, DbContexts, HttpClients, etc. so they can be injected elsewhere in the app.</li>
<li><b>Logging</b> — <span class="code">builder.Logging</span> lets you configure logging providers (console, debug, event source, etc.) out of the box.
Web server setup — configures Kestrel (the built-in web server) and host settings like URLs, content root, and environment name (Development/Staging/Production).</li>
</ol>
<p>Once you&#8217;ve configured everything on the builder, you call <span class="code">builder.Build()</span>, which returns a WebApplication instance — this represents the fully configured app, and it&#8217;s what you use afterward to set up middleware (<span class="code">app.Use..</span>) and endpoints (<span class="code">app.Map..</span>) before calling <span class="code">app.Run()</span>.</p>
<p><u>In short</u>: WebApplicationBuilder is a single unified object that replaces the old IWebHostBuilder/IHostBuilder split, making it much simpler to configure services, configuration, and logging in one place before the app is built and started.</p>
<p><b>3. What is Kestrel ?</b></p>
<p>Kestrel is the built-in, cross-platform web server used by ASP.NET Core to handle HTTP requests. It&#8217;s the default server that gets automatically configured and started for you when you call <span class="code">app.Run()</span> — you don&#8217;t need to install IIS, Apache, or anything else to run an ASP.NET Core app. Key characteristics are:</p>
<ol>
<li>Cross-platform — Kestrel runs on Windows, Linux, and macOS, since it&#8217;s built on top of libuv/managed sockets (in newer versions, it uses a fully managed socket implementation) rather than any OS-specific web server technology.</li>
<li>Fast and lightweight — it&#8217;s optimized for throughput and is one of the fastest .NET web servers, benchmarked regularly in the TechEmpower benchmarks.</li>
<li>Included by default — when you call WebApplication.CreateBuilder(args), Kestrel is configured automatically as the web server. You don&#8217;t add it explicitly in most apps.</li>
<li>Not always exposed directly to the internet — in production, Kestrel is often run behind a reverse proxy like:
<ul>
<li>IIS (on Windows)</li>
<li>Nginx or Apache (on Linux)</li>
<li>Azure App Service&#8217;s built-in proxy</li>
</ul>
</li>
</ol>
<p>The reverse proxy handles things like SSL termination, request buffering, load balancing across multiple Kestrel instances, and serving static files more efficiently — while Kestrel focuses purely on handling the ASP.NET Core application logic. That said, Kestrel is robust enough to be exposed directly to the internet these days (it has built-in support for HTTPS, HTTP/2, HTTP/3, connection limits, timeouts, etc.), so this pattern is now optional rather than mandatory.</p>
<p><b>How you configure it:</b></p>
<p>You can customize Kestrel through <span class="code">builder.WebHost.ConfigureKestrel(&#8230;)</span> or in <span class="term">appsettings.json</span>:</p>



<pre class="wp-block-code"><code>var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =&gt;
{
    options.ListenAnyIP(5000); // HTTP
    options.ListenAnyIP(5001, listenOptions =&gt;
    {
        listenOptions.UseHttps(); // HTTPS
    });

    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB
});</code></pre>



<p><b>Or via configuration:</b></p>



<pre class="wp-block-code"><code>{
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "http://localhost:5000"
      },
      "Https": {
        "Url": "https://localhost:5001"
      }
    }
  }
}</code></pre>



<p><b>Where it fits in the pipeline:</b></p>



<pre class="wp-block-code"><code>Client Request
      ↓
&#91;Reverse Proxy - optional] (Nginx / IIS / Azure)
      ↓
   Kestrel  ← low-level HTTP server, receives raw requests
      ↓
Middleware Pipeline (app.Use...)
      ↓
Endpoints (app.Map... / Controllers)</code></pre>



<p>In short: Kestrel is the actual server process listening on a port and translating raw HTTP traffic into requests that flow through your ASP.NET Core middleware pipeline. It&#8217;s what makes <span class="code">app.Run()</span> actually able to &#8220;run&#8221; — without it, there&#8217;d be nothing accepting connections.</p>
<p><b>4. What is the ASP.NET Core request pipeline?</b></p>
<p>The request pipeline is the sequence of middleware components that every incoming HTTP request passes through before a response is generated and sent back. Each middleware can inspect, modify, short-circuit, or pass along the request — and then do the same on the way back out.</p>
<p>A typical pipeline in <span class="term">Program.cs</span>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var app = builder.Build();

app.UseExceptionHandler(&quot;/Error&quot;);   // catches exceptions from everything below
app.UseHsts();                       // adds HSTS header (production)

app.UseHttpsRedirection();           // redirect HTTP -&gt; HTTPS
app.UseStaticFiles();                // serve wwwroot files, short-circuits if found

app.UseRouting();                    // determines which endpoint matches the URL

app.UseCors();                       // apply CORS policy
app.UseAuthentication();             // who are you? (sets HttpContext.User)
app.UseAuthorization();              // are you allowed? (checks policies)

app.MapControllers();                // executes the matched endpoint
app.Run();
</pre></div>


<p>Middleware executes in the order it&#8217;s registered, and typically in a &#8220;pipeline&#8221; shape:</p>
<p>For example, <span class="code">UseAuthentication()</span> must come before <span class="code">UseAuthorization()</span>, and both generally come after <span class="code">UseRouting()</span> but before <span class="code">UseEndpoints()</span>.</p>
<p><b>5. What is the difference between Use, Run, and Map?</b></p>
<p>These are the three core extension methods (from IApplicationBuilder) for building the middleware pipeline. They differ in whether they call the next middleware, whether they branch, and how they match requests.</p>
<p><b>Use — chain middleware, call next()</b></p>
<p><span class="code">Use</span> adds middleware to the pipeline that can call the next middleware and continue the chain. This is the most common one.</p>



<pre class="wp-block-code"><code>app.Use(async (context, next) =&gt;
{
    Console.WriteLine("Before");
    await next(); // passes control to the next middleware
    Console.WriteLine("After");
});</code></pre>



<ul>
<li>Can inspect/modify the request before <span class="code">next()</span>.</li>
<li>Can inspect/modify the response after <span class="code">next()</span> returns.</li>
<li>Can choose not to call <span class="code">next()</span> (short-circuit), though that&#8217;s more Run&#8217;s job conceptually.</li>
<li>Multiple Use calls form a chain, each wrapping the next.</li>
</ul>
<p><b>Run — terminal middleware, no next</b></p>
<p><span class="code">Run</span> adds terminal middleware — it does not receive a next parameter at all, because it&#8217;s meant to end the pipeline. Whatever is registered after a Run will never execute for that branch.</p>



<pre class="wp-block-code"><code>app.Run(async context =&gt;
{
    await context.Response.WriteAsync("Hello, this is the end of the line.");
});</code></pre>



<ul>
<li>No way to call anything further — it&#8217;s a dead end by design.</li>
<li>Typically used as the last piece of a pipeline or a branch (e.g., inside Map/MapWhen).</li>
<li>If you add middleware after app.Run(), it&#8217;s simply never reached — the compiler won&#8217;t stop you, but it&#8217;s dead code.</li>
</ul>
<p><b>Map — branch the pipeline by path</b></p>
<p><span class="code">Map</span> creates a separate sub-pipeline based on a matching request path. Once a request matches, it&#8217;s routed into that branch instead of continuing in the main pipeline.</p>



<pre class="wp-block-code"><code>app.Map("/admin", adminApp =&gt;
{
    adminApp.Use(async (context, next) =&gt;
    {
        Console.WriteLine("Inside /admin branch");
        await next();
    });

    adminApp.Run(async context =&gt;
    {
        await context.Response.WriteAsync("Admin area");
    });
});

app.Run(async context =&gt;
{
    await context.Response.WriteAsync("Main pipeline");
});</code></pre>



<ul>
<li>Matches based on the path segment (<span class="term">/admin</span>, <span class="term">/api</span>, etc.) — it strips the matched segment off PathBase for the branch.</li>
<li>Once branched, that request stays in the branch&#8217;s pipeline (it doesn&#8217;t automatically rejoin the main one).</li>
<li>There&#8217;s also <span class="code">MapWhen()</span>, which branches based on any condition, not just path.</li>
</ul>
<p><b>Important distinction: this is different from app.MapGet/MapControllers</b></p>
<p>Don&#8217;t confuse <span class="code">app.Map(&#8230;)</span> (pipeline branching, from &#8220;IApplicationBuilder&#8221;) with <span class="code">app.MapGet(&#8230;)</span>, <span class="code">app.MapPost(&#8230;)</span>, <span class="code">app.MapControllers()</span> (from <u>IEndpointRouteBuilder</u>) — the endpoint routing methods used after <span class="code">UseRouting()</span>. Those register actual endpoints (minimal APIs, controllers) rather than branching middleware.</p>



<pre class="wp-block-code"><code>app.UseRouting();

app.MapGet("/hello", () =&gt; "Hi!");   // endpoint mapping, not pipeline branching
app.MapControllers();                 // endpoint mapping</code></pre>



<p><b>6. What is Routing also explain endpoint routing. What does app.UseRouting() do ?</b></p>
<p>Routing is the process by which ASP.NET Core matches an incoming HTTP request to an endpoint — a piece of code that can handle that request (a controller action, Razor Page, minimal API delegate, gRPC service, etc.).</p>
<p>It looks at things like:</p>
<ul>
<li>The URL path (<span class="code">/products/5</span>).</li>
<li>The HTTP method (GET, POST, etc).</li>
<li>Sometimes headers, query strings, or route constraints.</li>
</ul>
<p>&#8230;and decides which handler should execute, and extracts parameters from the URL (like <span class="code">id = 5</span>) to pass into that handler.</p>
<h3>What is Endpoint Routing ?</h3>
<p>Before ASP.NET Core 2.2, routing and execution were tightly coupled — the router matched a route and immediately dispatched to a handler in one step. This made it hard for other middleware (like CORS, Authorization) to know in advance which endpoint would run, because that information wasn&#8217;t available until the MVC middleware itself resolved it.</p>
<p>Endpoint Routing (introduced in 2.2, standard since 3.0) splits this into two distinct phases:</p>
<ol>
<li>Route Matching — figure out which endpoint matches the request, and expose that as <span class="code">HttpContext.GetEndpoint()</span>.</li>
<li>Route Execution — actually invoke that endpoint.</li>
</ol>
<p>Because these are now separate, middleware placed between matching and execution can inspect the selected endpoint (and its metadata, like <span class="code">[Authorize]</span> attributes) and make decisions accordingly — without having to duplicate routing logic itself.</p>
<p>Why this matters (good interview point):</p>
<div class="noteBlock">&#8220;Endpoint routing decouples matching from execution. This lets middleware like Authorization or CORS run after the endpoint is known but before it executes, so they can read metadata off the endpoint (like <span class="code">[Authorize(Roles=&#8221;Admin&#8221;)]</span>) and enforce policies correctly.&#8221;</div>
<h3>What does app.UseRouting() do?</h3>
<p><span class="code">app.UseRouting()</span> adds the route matching middleware to the pipeline. It:</p>
<ul>
<li>Looks at the incoming request.</li>
<li>Matches it against the registered route patterns (from <span class="term">MapControllers(), MapGet(), MapRazorPages()</span>, etc.)</li>
<li>Sets <span class="code">HttpContext.GetEndpoint()</span> to the matched endpoint.</li>
<li>Does NOT execute the endpoint yet — it just identifies it.</li>
</ul>
<p>The actual execution happens later — either implicitly via <span class="term">app.MapControllers() / app.MapGet()</span> (in .NET 6+ minimal hosting), or explicitly via <span class="code">app.UseEndpoints(&#8230;)</span> in older Program.cs / Startup.cs style.</p>
<p>Typical middleware order (very commonly asked):</p>



<pre class="wp-block-code"><code>app.UseRouting();          // 1. Match the request to an endpoint

app.UseCors();             // 2. Runs AFTER routing, BEFORE execution —
app.UseAuthentication();   //    these can inspect the matched endpoint
app.UseAuthorization();    //    (e.g., check &#91;Authorize] metadata)

app.MapControllers();      // 3. Execute the matched endpoint</code></pre>



<p><b>Why order matters (classic gotcha question):</b></p>
<p>If you call <span class="code">UseAuthorization()</span> before <span class="code">UseRouting()</span>, it will throw an exception or fail to work correctly — because at that point, no endpoint has been matched yet, so there&#8217;s no metadata (like <span class="code">[Authorize]</span>) for the authorization middleware to inspect.</p>
<p><b>7. What is app.UseAuthentication() and app.UseAuthorization() ?</b></p>
<p><span class="code">app.UseAuthentication()</span> adds the authentication middleware to the pipeline. Its job is to figure out &#8220;who is making this request?&#8221;</p>
<ul>
<li>It examines the incoming request (cookies, JWT bearer tokens, headers, etc).</li>
<li>Runs the configured authentication scheme(s) (Cookie, JWT Bearer, OAuth, OpenID Connect, etc).</li>
<li>If valid credentials are found, it constructs a ClaimsPrincipal and attaches it to <span class="code">HttpContext.User</span>.</li>
<li>If no valid credentials exist, <span class="code">HttpContext.User</span> is set to an unauthenticated ClaimsPrincipal (not null — just <span class="term">IsAuthenticated == false)</span>.</li>
</ul>
<div class="noteBlock">&#8220;Authentication answers: who are you? It populates <span class="code">HttpContext.User</span> based on the credentials in the request.&#8221;</div>



<pre class="wp-block-code"><code>builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer();

app.UseAuthentication(); // sets HttpContext.User</code></pre>



<p><span class="code">app.UseAuthorization()</span> adds the authorization middleware. Its job is to figure out &#8220;are you allowed to do this?&#8221;</p>
<ul>
<li>It looks at HttpContext.User (populated by authentication).</li>
<li>Checks it against the requirements of the matched endpoint — e.g., [Authorize], [Authorize(Roles = &#8220;Admin&#8221;)], or policy-based requirements.</li>
<li>If the user doesn&#8217;t meet the requirements → returns 403 Forbidden (or 401 Unauthorized if not authenticated at all).</li>
<li>If they do → request proceeds to the endpoint.</li>
</ul>
<div class="noteBlock">&#8220;Authorization answers: given who you are, are you permitted to access this specific resource?&#8221;</div>



<pre class="wp-block-code"><code>&#91;Authorize(Roles = "Admin")]
public IActionResult DeleteUser(int id) { ... }</code></pre>



<p><b>Why Order Matters (classic gotcha):</b></p>



<pre class="wp-block-code"><code>app.UseRouting();          // 1. Match request to endpoint (sets HttpContext.GetEndpoint())
app.UseAuthentication();   // 2. Determine WHO the user is → sets HttpContext.User
app.UseAuthorization();    // 3. Determine IF that user can access the matched endpoint
app.MapControllers();      // 4. Execute the endpoint</code></pre>



<p>Key rules:</p>
<ul>
<li><span class="code">UseAuthentication()</span> must come before <span class="code">UseAuthorization()</span> — you can&#8217;t authorize a user you haven&#8217;t identified yet.</li>
<li>Both must come after <span class="code">UseRouting()</span> — authorization needs to know which endpoint was matched (to read its <span class="code">[Authorize]</span> metadata), which is only known after routing runs.</li>
<li>Both must come before <span class="code">MapControllers()</span> / endpoint execution — otherwise the checks never happen before the code runs.</li>
</ul>
<p>If you get the order wrong, ASP.NET Core will often literally throw a runtime exception telling you to fix the middleware order (there&#8217;s a diagnostic check for this).</p>
<p><b>8. What is the difference between IHostEnvironment and IWebHostEnvironment ?</b></p>
<p><span class="code">IHostEnvironment</span> is a generic interface providing information about the hosting environment for any .NET generic host — not specific to web apps. It&#8217;s part of <span class="term">Microsoft.Extensions.Hosting</span>, used by console apps, worker services, and web apps alike.</p>



<pre class="wp-block-code"><code>public interface IHostEnvironment
{
    string EnvironmentName { get; set; }     // Development, Staging, Production
    string ApplicationName { get; set; }      // Assembly/app name
    string ContentRootPath { get; set; }      // Root folder for content files
    IFileProvider ContentRootFileProvider { get; set; }
}</code></pre>



<p>Use case: Any generic host app (worker service, background service, console app) that just needs to know the environment name or content root — no concept of &#8220;web&#8221; involved.</p>
<p><span class="code">IWebHostEnvironment</span> is a web-specific extension of IHostEnvironment, part of Microsoft.AspNetCore.Hosting. It adds properties relevant only to web applications — specifically, serving static files.</p>



<pre class="wp-block-code"><code>public interface IWebHostEnvironment : IHostEnvironment
{
    string WebRootPath { get; set; }          // wwwroot folder path
    IFileProvider WebRootFileProvider { get; set; }
}</code></pre>



<p>Use case: ASP.NET Core web apps that need to know where wwwroot is, to serve static files (CSS, JS, images).</p>
<div class="noteBlock">&#8220;IHostEnvironment is the generic host abstraction — environment name, content root — usable in any .NET host. IWebHostEnvironment extends it with web-specific concerns, primarily the wwwroot path, since only web apps serve static files.&#8221;</div>
<p><b>9. What is IConfiguration?</b></p>
<p><span class="term">IConfiguration</span> is the core abstraction in ASP.NET Core for accessing configuration data — key/value settings your app needs at runtime — regardless of where that data actually comes from.</p>
<p>It&#8217;s part of Microsoft.Extensions.Configuration and is populated by the Generic Host before your app even starts handling requests.</p>
<div class="noteBlock">&#8220;IConfiguration is a unified, provider-agnostic way to read settings. Your code doesn&#8217;t care whether a value came from appsettings.json, an environment variable, or a command-line argument — it just asks IConfiguration for it.&#8221;</div>
<p>Where the data comes from (Configuration Providers):</p>
<p>By default, <span class="term">WebApplicationBuilder</span> wires up multiple providers, layered in this order (later ones override earlier ones):</p>
<ul>
<li>appsettings.json</li>
<li>appsettings.{Environment}.json</li>
<li>User Secrets (Development only)</li>
<li>Environment variables</li>
<li>Command-line arguments</li>
</ul>



<pre class="wp-block-code"><code>var builder = WebApplication.CreateBuilder(args);
// builder.Configuration is already an IConfiguration built from the above sources</code></pre>



<p>You can also add custom sources: Azure Key Vault, a database, XML/INI files, in-memory collections, etc.</p>
<p><b>10. What is appsettings.json ? How do environment-specific configuration files work ?</b></p>
<p><span class="term">appsettings.json</span> is the default configuration file in ASP.NET Core — a JSON file at the root of the project used to store application settings (connection strings, logging levels, feature flags, custom settings, etc.). It&#8217;s automatically loaded as one of the configuration providers when you call <span class="code">WebApplication.CreateBuilder(args)</span>.</p>



<pre class="wp-block-code"><code>{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=MyDb;Trusted_Connection=True;"
  },
  "AllowedHosts": "*"
}</code></pre>



<p>Alongside <span class="code">appsettings.json</span>, you can have environment-specific overrides:</p>



<pre class="wp-block-code"><code>appsettings.json                 ← base settings, always loaded
appsettings.Development.json     ← loaded only when EnvironmentName = "Development"
appsettings.Staging.json         ← loaded only when EnvironmentName = "Staging"
appsettings.Production.json      ← loaded only when EnvironmentName = "Production"</code></pre>



<p>How the environment is determined: Set via the <span class="term">ASPNETCORE_ENVIRONMENT (or DOTNET_ENVIRONMENT) environment variable — commonly Development, Staging, or Production</span>. Locally, this is usually set in <span class="code">launchSettings.json</span>.</p>
<p>Load Order &amp; Merging (the key interview point):</p>
<p><span class="code">WebApplication.CreateBuilder(args)</span> wires these up in a specific order, and later sources override earlier ones on matching keys:</p>
<ol>
<li>appsettings.json</li>
<li>appsettings.{EnvironmentName}.json</li>
<li>User Secrets (Development only)</li>
<li>Environment variables</li>
<li>Command-line arguments</li>
</ol>
<div class="noteBlock">&#8220;It&#8217;s not a replace — it&#8217;s a layered merge. appsettings.{Environment}.json only needs to specify the keys that differ; everything else falls back to the base appsettings.json.&#8221;</div>
<p>Example of the merge behavior</p>
<p>appsettings.json:</p>



<pre class="wp-block-code"><code>{
  "Logging": { "LogLevel": { "Default": "Information" } },
  "ApiUrl": "https://api.prod.example.com"
}</code></pre>



<p>appsettings.Development.json:</p>



<pre class="wp-block-code"><code>{
  "Logging": { "LogLevel": { "Default": "Debug" } }
}</code></pre>



<p>Result when running in Development:</p>



<pre class="wp-block-code"><code>{
  "Logging": { "LogLevel": { "Default": "Debug" } },   // overridden
  "ApiUrl": "https://api.prod.example.com"               // inherited from base
}</code></pre>



<div class="noteBlock">Only LogLevel.Default was overridden — ApiUrl was untouched because Development&#8217;s file never mentioned it.</div>
<p>How It&#8217;s Registered (behind the scenes) &#8211; <span class="code">WebApplication.CreateBuilder(args)</span> does roughly this internally:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
config.AddJsonFile(&quot;appsettings.json&quot;, optional: true, reloadOnChange: true);
config.AddJsonFile($&quot;appsettings.{env.EnvironmentName}.json&quot;, optional: true, reloadOnChange: true);
if (env.IsDevelopment())
{
    config.AddUserSecrets(...);
}
config.AddEnvironmentVariables();
config.AddCommandLine(args);
</pre></div>


<ul>
<li>optional: true → the app won&#8217;t crash if, say, appsettings.Production.json doesn&#8217;t exist.</li>
<li>reloadOnChange: true → the file is watched; if it changes on disk while the app is running, IConfiguration picks up the new values (paired with IOptionsMonitor<t> for live updates in your code).</t></li>
</ul>
<h2>Dependency Injection Interview Questions</h2>
<p>Dependency Injection questions are a staple in ASP.NET Core interviews because DI isn&#8217;t just a supporting feature — it&#8217;s baked into the framework&#8217;s core architecture. Interviewers ask about it to see whether you understand why the framework is built the way it is, not just that you can inject an interface into a constructor. Your answer reveals whether you grasp concepts like <b>loose coupling, testability, service lifetimes (Singleton/Scoped/Transient), the composition root, and how ASP.NET Core&#8217;s own internals (logging, configuration, EF Core) are wired using the same container</b>. A clear, structured answer here signals real hands-on experience rather than memorized definitions, and it sets you up well for follow-up questions on service lifetimes, captive dependencies, and the options pattern.</p>
<p><b>11. What is Dependency Injection(DI) ? Why DI is used in ASP.NET Core ? Explain the three DI lifetimes?</b></p>
<p>Dependency Injection is a design pattern where an object&#8217;s dependencies (the other objects/services it needs to function) are provided to it from the outside, rather than the object creating them itself.</p>
<p>Instead of this (tight coupling):</p>



<pre class="wp-block-code"><code>public class OrderService
{
    private readonly EmailService _emailService = new EmailService(); // hardcoded dependency
}</code></pre>



<p>You do this (loose coupling):</p>



<pre class="wp-block-code"><code>public class OrderService
{
    private readonly IEmailService _emailService;

    public OrderService(IEmailService emailService) // injected via constructor
    {
        _emailService = emailService;
    }
}</code></pre>



<p>The class depends on an abstraction (<span class="term">IEmailService</span>), and something external — a DI container — decides which concrete implementation to hand it at runtime.</p>
<h3>Why is DI used in ASP.NET Core ?</h3>
<p>ASP.NET Core has DI built into the framework itself (not bolted on like older versions needed third-party tools such as <u>Autofac</u> or <u>Ninject</u>). Reasons it&#8217;s used:</p>
<ul>
<li>Loose coupling — classes depend on interfaces, not concrete implementations, making code easier to change.</li>
<li>Testability — you can inject mock/fake implementations in unit tests instead of real services (e.g., mock a database call).</li>
<li>Maintainability — swapping an implementation (e.g., switching from SendGrid to SMTP for emails) means changing one registration line, not every class that uses it.</li>
<li>Centralized object lifetime management — the DI container manages creation and disposal of objects, so you&#8217;re not manually managing new and cleanup everywhere.</li>
<li>Framework-wide consistency — built-in services like ILogger, DbContext, IConfiguration, HttpClient are all provided via DI, so your custom services follow the same pattern.</li>
</ul>
<p>Registration happens in <span class="term">Program.cs</span>:</p>



<pre class="wp-block-code"><code>builder.Services.AddScoped&lt;IEmailService, EmailService&gt;();</code></pre>



<h3>Explain the three DI lifetimes</h3>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead class="table-primary"><tr><th>Lifetime</th><th>Instance created</th><th>Typical use case</th></tr></thead><tbody><tr><td><b>Transient</b></td><td>A new instance every time it&#8217;s requested</td><td>Lightweight, stateless services</td></tr><tr><td><b>Scoped</b></td><td>One instance per request (HTTP request scope)</td><td>Services that need consistency within a request, e.g., <code>DbContext</code></td></tr><tr><td><b>Singleton</b></td><td>One instance for the entire application lifetime</td><td>Shared state, caching, configuration, logging</td></tr></tbody></table></div>



<div class="note">a. Transient — AddTransient&lt;TInterface, TImplementation>()</div>
<ul>
<li>A new object is created every single time it&#8217;s injected — even multiple times within the same request.</li>
<li>Best for small, stateless, cheap-to-create services.</li>
</ul>



<pre class="wp-block-code"><code>builder.Services.AddTransient&lt;IEmailService, EmailService&gt;();</code></pre>



<div class="note">b. Scoped — AddScoped&lt;TInterface, TImplementation>()</div>
<ul>
<li>One instance is created per HTTP request. If the same service is requested multiple times within that request, the same instance is reused.</li>
<li>Classic example: <span class="term">DbContext</span> in EF Core — you want the same context throughout a request to track changes consistently, but not share it across requests (which would cause concurrency issues).</li>
</ul>



<pre class="wp-block-code"><code>builder.Services.AddScoped&lt;IOrderRepository, OrderRepository&gt;();</code></pre>



<div class="note">c. Singleton — AddSingleton&lt;TInterface, TImplementation&gt;()</div>
<ul>
<li>Only one instance for the entire lifetime of the application — created once, reused for every request by every user.</li>
<li>Good for: caching services, configuration objects, logging providers.</li>
<li>⚠️ Risk: if a singleton holds a reference to a scoped service (like DbContext), you get a captive dependency bug — the short-lived service gets trapped inside the long-lived one, causing threading/data issues.</li>
</ul>



<pre class="wp-block-code"><code>builder.Services.AddSingleton&lt;ICacheService, CacheService&gt;();</code></pre>



<p>Follow-up traps interviewers often ask:</p>
<ul>
<li>&#8220;What happens if you inject a scoped service into a singleton?&#8221; → Runtime exception (or captive dependency bug if done incorrectly via <span class="term">IServiceProvider</span>).</li>
<li>&#8220;Is DbContext scoped or transient by default?&#8221; → Scoped, via <span class="code">AddDbContext&lt;&gt;()</span>.</li>
<li>&#8220;Can you manually resolve a service instead of constructor injection?&#8221; → Yes, via <span class="code">IServiceProvider.GetService&lt;t&gt;()</span> (service locator pattern — generally discouraged).</li>
<li>&#8220;What&#8217;s the difference between DI and IoC (Inversion of Control)?&#8221; → DI is one way to implement the broader IoC principle.</li>
</ul>
<p><b>12. When should you use a Singleton service ?</b></p>
<p>Use <b>Singleton</b> when the service should be created once for the entire application lifetime and shared across all requests and all users.</p>
<p>Good use cases:</p>
<ul>
<li><span class="term">Stateless, thread-safe utility services</span> — e.g., a service that just does computation with no mutable shared state (ILogger-style helpers, mapping utilities).</li>
<li><span class="term">In-memory caching services</span> — e.g., IMemoryCache itself is registered as a singleton; a custom cache wrapper that holds data across requests.</li>
<li><span class="term">Configuration/settings</span> objects that are read-only after startup (e.g., wrapping IOptions<t> values you don&#8217;t expect to change).</t></li>
<li><span class="term">Connection pools / expensive-to-create shared resources</span> — e.g., an HttpClient factory setup, a database connection pool manager, or a third-party SDK client that&#8217;s explicitly documented as thread-safe and meant to be reused.</li>
<li><span class="term">Application-wide counters, background job schedulers, or shared state coordinators</span> — e.g., a service tracking metrics across the app, or a coordinator for a BackgroundService.</li>
<li><span class="term">Expensive initialization</span> — if constructing the service is costly (loading large data, compiling regex, etc.), singleton avoids repeating that cost per request.</li>
</ul>
<p><u>Key requirement: thread safety:</u></p>
<p>Because a singleton instance is shared across concurrent requests, it must be thread-safe. Any mutable state inside it needs proper synchronization (locks, ConcurrentDictionary, immutable data structures, etc.), or you risk race conditions.</p>
<p><u>When NOT to use Singleton:</u></p>
<ul>
<li>If the service depends on Scoped services (like DbContext), don&#8217;t make it a singleton — this causes the classic &#8220;captive dependency&#8221; problem, where the scoped service gets trapped inside a singleton and effectively becomes a singleton too, often causing bugs (e.g., a DbContext used across threads/requests incorrectly).</li>
<li>If the service holds per-request or per-user state — use Scoped instead.</li>
<li>If it has cheap-to-create, non-thread-safe logic with no shared state — Transient is simpler and safer.</li>
</ul>
<p><u>Interview-ready summary:</u></p>
<p>Use Singleton for stateless or thread-safe shared services that are expensive to create or need to maintain state across the entire app lifetime — like caching, configuration, or connection pooling — while making sure they never directly depend on Scoped services like DbContext.</p>
<p><b>13. Why is DbContext normally registered as Scoped ?</b></p>
<p><u>a. It maps naturally to a single unit of work:</u></p>
<p>A web request typically represents one logical &#8220;unit of work&#8221; — read some data, maybe modify it, save changes, done. DbContext is designed around this same idea: it tracks changes to entities, batches them, and commits them together via SaveChanges(). Scoping it to the request means each request gets a clean, isolated unit of work that starts and ends with the request.</p>
<p><u>b. DbContext is not thread-safe</u></p>
<p>A single DbContext instance cannot be used by multiple threads concurrently — EF Core will throw exceptions if you try. If it were a Singleton, every concurrent request would share the same instance and could corrupt or crash things under load. Scoped guarantees each request gets its own instance, so there&#8217;s no cross-request interference.</p>
<p><u>c. Change tracking needs a clear boundary</u></p>
<p>DbContext keeps an internal cache of entities it&#8217;s tracking (the change tracker). If it lived for the whole app lifetime (Singleton), this cache would grow unbounded and get stale — you&#8217;d risk serving outdated data or accidentally saving changes that belong to a completely different request/user.</p>
<p><u>d. Avoids &#8220;captive dependency&#8221; problems</u></p>
<p>If DbContext were Scoped but got injected into a Singleton service, it would become a captive dependency — trapped inside the singleton for the app&#8217;s whole life, defeating its purpose and likely causing threading/staleness bugs. Keeping DbContext itself Scoped, and being careful about what depends on it, keeps this boundary clean.</p>
<p><u>e. Matches connection lifetime expectations</u></p>
<p>Scoped lifetime aligns well with how the underlying database connection should be used — opened for the duration of the request/unit of work, then released back to the pool. Not held open indefinitely (Singleton), and not needlessly reopened many times within the same logical operation (which could happen if it were Transient and injected into multiple places within one request).</p>
<div class="noteBlock">
DbContext is Scoped because it represents a single unit of work tied to a request: it&#8217;s not thread-safe, tracks entity state that shouldn&#8217;t leak across requests, and needs a clear start/end boundary — Singleton would cause thread-safety and stale-data issues, while Transient would fragment change tracking within a single request if injected into multiple services.</div>
<p><b>14. What is constructor injection ? Can ASP.NET Core perform property injection ?
</b></p>
<p>Constructor injection is a dependency injection technique where a class receives its dependencies as parameters through its constructor, rather than creating them itself or having them set via properties/methods.</p>



<pre class="wp-block-code"><code>public class OrderService
{
    private readonly ILogger&lt;OrderService&gt; _logger;
    private readonly IEmailSender _emailSender;

    // Dependencies are "injected" through the constructor
    public OrderService(ILogger&lt;OrderService&gt; logger, IEmailSender emailSender)
    {
        _logger = logger;
        _emailSender = emailSender;
    }

    public void PlaceOrder()
    {
        _logger.LogInformation("Order placed");
        _emailSender.Send("Order confirmed");
    }
}</code></pre>



<p>Instead of OrderService doing new <span class="code">EmailSender()</span> internally, it declares &#8220;I need an an IEmailSender to function&#8221; — and something external (the DI container) supplies them when creating the object.</p>
<p><u>How it works in ASP.NET Core:</u></p>
<p>a. You register the dependency and its implementation in Program.cs:</p>



<pre class="wp-block-code"><code>builder.Services.AddScoped&lt;IEmailSender, EmailSender&gt;();
builder.Services.AddScoped&lt;OrderService&gt;();</code></pre>



<p>b. When you ask the container to create an OrderService (e.g., because it&#8217;s injected into a controller), it:</p>
<ul>
<li>Looks at the constructor &#8211; See it needs <span class="term">ILogger&lt;OrderService></span> and  <span class="term">IEmailSender</span></li>
<li>Resolves those from the container (recursively resolving their dependencies too)</li>
<li>Passes them into the constructor</li>
<li>Returns you a fully-constructed <span class="code">OrderService</span>.</li>
</ul>



<pre class="wp-block-code"><code>public class OrdersController : ControllerBase
{
    private readonly OrderService _orderService;

    public OrdersController(OrderService orderService) // &lt;- injected automatically
    {
        _orderService = orderService;
    }
}</code></pre>



<p><u>Why it&#8217;s preferred (key interview points):</u></p>



<div class="table-responsive">
<table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Benefit</th><th>Explanation</th></tr></thead><tbody><tr><td><b>Explicit dependencies</b></td><td>Anyone reading the constructor immediately knows what the class needs to work.</td></tr><tr><td><b>Guaranteed valid state</b></td><td>The object can&#8217;t exist without its required dependencies — no null-reference surprises later.</td></tr><tr><td><b>Immutability</b></td><td>Dependencies can be assigned to <code>readonly</code> fields, since they&#8217;re only set once at construction.</td></tr><tr><td><b>Testability</b></td><td>Easy to pass in mocks/fakes in unit tests without a DI container.</td></tr><tr><td><b>Fail-fast</b></td><td>If a dependency isn&#8217;t registered, you get an exception immediately at startup (or first resolution) rather than a silent null property later.</td></tr></tbody></table></div>



<h3>ASP.NET Core and Property Injection</h3>
<p>ASP.NET Core&#8217;s built-in dependency injection (DI) container does not support property injection out of the box. It only supports constructor injection.</p>
<p>The built-in <span class="term">IServiceProvider/IServiceCollection</span> container resolves dependencies exclusively by looking at a class&#8217;s constructor parameters. If you have a public property decorated with something like <span class="code">[Inject]</span> (as you might see in other frameworks or in Blazor components), the core DI container will simply ignore it — it won&#8217;t populate that property automatically.</p>



<pre class="wp-block-code"><code>public class MyService
{
    // This will NOT be automatically injected by the built-in container
    public ILogger&lt;MyService&gt; Logger { get; set; }

    // Only this works with built-in DI
    public MyService(ILogger&lt;MyService&gt; logger)
    {
        Logger = logger;
    }
}</code></pre>



<p>Blazor components are a notable exception — they use the <span class="code">[Inject]</span> attribute for property injection, but this is handled by Blazor&#8217;s component activation pipeline, not the general-purpose ASP.NET Core DI container.</p>



<pre class="wp-block-code"><code>public class MyComponent : ComponentBase
{
    &#91;Inject]
    public IMyService MyService { get; set; }
}</code></pre>



<p><b>15. What is IServiceProvider ? What is service resolution ?</b></p>
<p><span class="term">IServiceProvider</span> is the core interface in .NET&#8217;s dependency injection system that represents a container capable of resolving (creating/retrieving) service instances. It&#8217;s the fundamental abstraction that all of ASP.NET Core&#8217;s DI is built on top of.</p>
<p>It&#8217;s surprisingly simple — just one method:</p>



<pre class="wp-block-code"><code>public interface IServiceProvider
{
    object? GetService(Type serviceType);
}</code></pre>



<p>That&#8217;s it. Given a Type, it returns an instance of that type (or null if it&#8217;s not registered). Everything else — AddScoped, AddSingleton, constructor injection, etc. — is built as convenience layers on top of this single method.</p>
<p><i>Relationship to <u>IServiceCollection</u>:</i></p>
<p>These two work together but serve different roles:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Interface</th><th>Role</th><th>When used</th></tr></thead><tbody><tr><td><b>IServiceCollection</b></td><td><b>Registration</b> — a list of service descriptors (what maps to what)</td><td>During app startup/configuration</td></tr><tr><td><b>IServiceProvider</b></td><td><b>Resolution</b> — actually creates instances on demand</td><td>At runtime, when something needs a dependency</td></tr></tbody></table></div>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// IServiceCollection: registering
var services = new ServiceCollection();
services.AddScoped&lt;IEmailSender, EmailSender&gt;();

// Build the container -&gt; get an IServiceProvider
IServiceProvider provider = services.BuildServiceProvider();

// IServiceProvider: resolving
var emailSender = provider.GetService&lt;IEmailSender&gt;();
</pre></div>


<p>In <span class="term">Program.cs, builder.Services is an IServiceCollection</span>. When you call <span class="code">builder.Build()</span>, ASP.NET Core internally calls <span class="code">BuildServiceProvider()</span> to produce the <span class="term">IServiceProvider</span> that powers the app for its whole lifetime.</p>
<div class="starBlock"><p>Interview-ready summary:</p>
<p>&#8220;IServiceProvider is the interface representing the DI container itself — it has a single method, GetService(Type), that resolves and returns an instance of a requested type. While IServiceCollection is used at startup to register services, IServiceProvider is used at runtime to resolve them. ASP.NET Core builds an IServiceProvider from the IServiceCollection when the app starts, and the framework uses it internally to satisfy constructor injection. You&#8217;d interact with it directly mainly for manual resolution or when you need to create a scope — for example, to consume a scoped service from within a singleton via IServiceScopeFactory.&#8221;</p>
</div>



<h3>What is service resolution ?</h3>
<p>Service resolution is the runtime process of the DI container producing an actual instance of a requested type, based on what was registered earlier. It works recursively — the container inspects the requested type&#8217;s constructor, resolves each dependency (and their dependencies, and so on), then builds the object graph bottom-up. The instance returned — and whether it&#8217;s newly created or reused — depends on the service&#8217;s registered lifetime: transient, scoped, or singleton. Resolution typically happens automatically when ASP.NET Core constructs controllers or other framework-managed types, but can also be done manually via IServiceProvider.GetService or GetRequiredService.</p>



<pre class="wp-block-code"><code>// 1. Registration (startup) — just metadata, nothing is created yet
builder.Services.AddScoped&lt;IEmailSender, EmailSender&gt;();
builder.Services.AddScoped&lt;OrderService&gt;();

// 2. Resolution (runtime) — container actually builds the object graph
var orderService = provider.GetRequiredService&lt;OrderService&gt;();</code></pre>



<p class="wp-block-paragraph">When step 2 runs, the container:</p>



<ol class="wp-block-list">
<li>Looks up <code>OrderService</code> → finds its constructor needs <code>IEmailSender</code> (and maybe <code>ILogger&lt;OrderService&gt;</code>)</li>



<li>Resolves <code>IEmailSender</code> → finds it maps to <code>EmailSender</code> → checks <em>its</em> constructor for dependencies</li>



<li>Keeps resolving recursively until every leaf dependency is satisfied</li>



<li>Builds the objects bottom-up and passes them into constructors</li>



<li>Returns the fully-constructed <code>OrderService</code></li>
</ol>



<p class="wp-block-paragraph">This recursive process is often called building the <strong>object graph</strong> or <strong>dependency graph</strong>.</p>



<div class="note">Where resolution happens automatically vs. manually</div>



<p class="wp-block-paragraph"><strong>Automatic (most common):</strong> The framework resolves for you.</p>



<pre class="wp-block-code"><code>public class OrdersController : ControllerBase
{
    // ASP.NET Core resolves OrderService (and everything it needs)
    // automatically when creating this controller for an incoming request
    public OrdersController(OrderService orderService) { }
}</code></pre>



<p class="wp-block-paragraph"><strong>Manual:</strong> You explicitly ask the <code>IServiceProvider</code> to resolve something.</p>



<pre class="wp-block-code"><code>var service = provider.GetRequiredService&lt;OrderService&gt;();</code></pre>



<h2>Middleware Interview Questions</h2>
<p>Middleware questions come up a lot in ASP.NET Core interviews because they test whether you actually understand the framework rather than just knowing syntax. Here&#8217;s why interviewers lean on them:</p>
<p><u>a. It reveals whether you understand the request pipeline, not just controllers</u></p>
<p>A lot of developers can write a controller action but have never thought about everything that happens before and after it. Middleware questions expose whether you understand the full lifecycle of a request — which is foundational to debugging real production issues (weird headers, auth failures, CORS errors, etc. almost always trace back to middleware order).</p>
<p><u>b. Order-of-execution bugs are extremely common in real jobs</u></p>
<p>Bugs like &#8220;authorization isn&#8217;t working,&#8221; &#8220;CORS is broken,&#8221; or &#8220;exceptions aren&#8217;t being caught&#8221; are very often just middleware registered in the wrong order. Interviewers ask this because it&#8217;s a real, recurring source of production bugs — not an academic gotcha.</p>
<p><u>c. It tests architectural thinking (separation of concerns)</u></p>
<p>Knowing why logging/auth/error-handling belong in middleware rather than in every controller shows you understand clean architecture principles — separating cross-cutting concerns from business logic. This is a signal of engineering maturity, not just framework trivia.</p>
<p><u>d. It&#8217;s a natural way to probe DI lifetime understanding too</u></p>
<p>The classic &#8220;middleware constructor runs once, InvokeAsync runs per-request&#8221; gotcha tests whether you understand singleton vs. scoped/transient lifetimes — connecting two topics (DI + middleware) in one question. It&#8217;s an efficient way for an interviewer to check multiple concepts at once.</p>
<p><u>e. Custom middleware = a proxy for &#8220;can you extend the framework&#8221;</u></p>
<p>Asking you to write custom middleware checks if you can work with the framework&#8217;s conventions (the InvokeAsync(HttpContext, RequestDelegate) pattern) rather than fighting against them — a good signal of whether you&#8217;ll be productive quickly on a real codebase.</p>
<p><u>f. It often leads into follow-up questions</u></p>
<p>Middleware is a great &#8220;hub&#8221; topic — a good interviewer will pivot from it into:</p>
<ul>
<li>Exception handling strategies.</li> 
<li>(UseExceptionHandler vs. try/catch)
Authentication/authorization pipeline
CORS configuration.</li>
<li>Short-circuiting and performance implications
Filters vs. middleware (a classic follow-up: &#8220;when would you use an action filter instead of middleware?&#8221;).</li>
</ul>
<p><b>16. What is middleware ? How does middleware execute ? How do you create custom middleware ?</b></p>
<p>Middleware is a piece of code that runs on every request (or requests matching some condition), sitting between the raw incoming HTTP request and your endpoint/controller — able to inspect, modify, short-circuit, or pass along the request/response.</p>
<p>ASP.NET Core builds the pipeline by chaining middleware components together, one after another:</p>



<pre class="wp-block-code"><code>Request  →  Middleware A  →  Middleware B  →  Middleware C  →  Endpoint
Response ←  Middleware A  ←  Middleware B  ←  Middleware C  ←  Endpoint</code></pre>



<p>Each middleware component has the chance to:</p>
<ul>
<li>Do something before passing control to the next component (on the way in)</li>
<li>Decide whether to call the next component at all (or short-circuit)</li>
<li>Do something after the next component finishes (on the way out, with the response)</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var app = builder.Build();

app.Use(async (context, next) =&gt;
{
    Console.WriteLine(&quot;Before&quot;); // runs on the way in
    await next(context);         // calls the next middleware
    Console.WriteLine(&quot;After&quot;);  // runs on the way out
});

app.Run(async context =&gt;
{
    await context.Response.WriteAsync(&quot;Hello World&quot;);
});

app.Run();
</pre></div>


<p>For a request, the output would be:</p>



<pre class="wp-block-code"><code>Before
(response generated: "Hello World")
After</code></pre>



<p>Real world examples : Every ASP.NET Core app is built from middleware — you&#8217;ve likely used these without necessarily calling them &#8220;middleware&#8221;:</p>



<pre class="wp-block-code"><code>app.UseHttpsRedirection();   // redirects HTTP -&gt; HTTPS
app.UseStaticFiles();        // serves files from wwwroot
app.UseRouting();            // figures out which endpoint matches the URL
app.UseAuthentication();     // identifies who the user is
app.UseAuthorization();      // checks if they're allowed to access the resource
app.UseCors();               // handles cross-origin request rules
app.MapControllers();        // the "final" middleware that invokes your controller action</code></pre>



<h3>How Middleware Executes in ASP.NET Core ?</h3>
<p>ASP.NET Core middleware executes as an ordered pipeline of request delegates. Each component can perform logic before and after calling the next middleware via <span class="code">next()</span>, forming a nested chain. Order of registration determines execution order for the request path and the reverse order for the response path. Middleware can also short-circuit the pipeline by not calling <span class="code">next()</span>, which is how things like authentication or static file middleware avoid unnecessary processing.</p>
<h3>How do you create custom middleware ?</h3>
<p>This is the standard approach for real-world, reusable middleware. No interface is required — just follow a convention:</p>
<ul>
<li>Constructor takes <span class="code">RequestDelegate next</span>.</li>
<li>Public method named Invoke or <span class="code">InvokeAsync</span> that takes <span class="term">HttpContext</span> and returns <span class="code">Task</span></li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger&lt;RequestLoggingMiddleware&gt; _logger;

    public RequestLoggingMiddleware(RequestDelegate next, ILogger&lt;RequestLoggingMiddleware&gt; logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation(&quot;Handling request: {Path}&quot;, context.Request.Path);

        await _next(context); // pass control to next middleware

        _logger.LogInformation(&quot;Finished handling request. Status: {StatusCode}&quot;, 
            context.Response.StatusCode);
    }
}
</pre></div>


<p>Register it — typically via an extension method for cleanliness:</p>



<pre class="wp-block-code"><code>public static class RequestLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware&lt;RequestLoggingMiddleware&gt;();
    }
}</code></pre>



<p class="wp-block-paragraph">In the Program class:</p>



<pre class="wp-block-code"><code>// Program.cs
app.UseRequestLogging();</code></pre>



<p><b>17. What is the difference between middleware and filters ?</b></p>
<p>Both Middleware and Filters let you hook into the request pipeline, but they operate at different levels and have different levels of context awareness.</p>



<div class="note">Core Difference</div>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Aspect</th><th>Middleware</th><th>Filters</th></tr></thead><tbody><tr><td><b>Level</b></td><td>Application-level (raw HTTP pipeline)</td><td>MVC/Action-level (inside the MVC framework)</td></tr><tr><td><b>Awareness of MVC</b></td><td>None — knows nothing about controllers, actions, model binding</td><td>Full — knows about action methods, model state, controller context</td></tr><tr><td><b>Scope</b></td><td>Runs for <b>every</b> request (unless branched/short-circuited)</td><td>Runs only for requests that reach the MVC/Razor Pages pipeline, and can be scoped per-controller/action</td></tr><tr><td><b>Access to</b></td><td><code>HttpContext</code> only</td><td><code>HttpContext</code> <b>+</b> <code>ActionContext</code>, model binding results, action arguments, action result</td></tr><tr><td><b>Configured via</b></td><td><code>app.Use...()</code> in <code>Program.cs</code></td><td>Attributes, or registered globally in <code>AddControllers(options =&gt; options.Filters.Add(...))</code></td></tr></tbody></table></div>



<div class="note">Where They Sit in the Pipeline</div>



<p class="wp-block-paragraph">Filters actually execute <strong>inside</strong> one particular middleware — the MVC/endpoint-invocation middleware (<code>UseRouting</code> + <code>UseEndpoints</code>, effectively). So the relationship looks like:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
Request
  → Middleware 1
    → Middleware 2 (e.g., Routing)
      → Middleware 3 (Endpoint execution)
        → Authorization Filter
          → Resource Filter
            → Model Binding
              → Action Filter (before)
                → Action Method executes
              → Action Filter (after)
            → Exception Filter (if needed)
          → Result Filter (before)
            → Result executes (e.g., serialize to JSON)
          → Result Filter (after)
      ← Middleware 3
    ← Middleware 2
  ← Middleware 1
Response
</pre></div>


<div class="note">Filter Types (MVC-specific granularity)</div>



<p class="wp-block-paragraph">Filters have <strong>multiple specialized stages</strong>, which middleware doesn&#8217;t have:</p>



<ol class="wp-block-list">
<li><strong>Authorization Filters</strong> — run first, decide if user is allowed</li>



<li><strong>Resource Filters</strong> — run before model binding (good for caching)</li>



<li><strong>Action Filters</strong> — run immediately before/after the action method</li>



<li><strong>Exception Filters</strong> — handle exceptions thrown by action methods</li>



<li><strong>Result Filters</strong> — run before/after the action result is executed (e.g., before the view/JSON is written to response)</li>
</ol>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class LogActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Before the action method runs — has access to action arguments
        Console.WriteLine($&quot;Executing {context.ActionDescriptor.DisplayName}&quot;);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // After the action method runs — has access to the result
        Console.WriteLine($&quot;Executed. Result: {context.Result}&quot;);
    }
}
</pre></div>


<p class="wp-block-paragraph">Registration options:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;ServiceFilter(typeof(LogActionFilter))]  // on a specific action/controller
public IActionResult Get() =&gt; Ok();

// OR globally:
builder.Services.AddControllers(options =&gt;
{
    options.Filters.Add&lt;LogActionFilter&gt;();
});
</pre></div>


<div class="note">When to Use Which</div>



<p class="wp-block-paragraph"><strong>Use Middleware when:</strong></p>



<ul class="wp-block-list">
<li>Logic applies to <strong>all</strong> requests regardless of framework (e.g., static files, HTTPS redirection, CORS, logging every request, custom headers)</li>



<li>You need to short-circuit before reaching MVC at all</li>



<li>You don&#8217;t need action-specific context (route values, model state, action arguments)</li>
</ul>



<p class="wp-block-paragraph"><strong>Use Filters when:</strong></p>



<ul class="wp-block-list">
<li>You need MVC-specific context (which action is being called, model binding results, <code>[FromBody]</code> arguments)</li>



<li>You want granular control tied to specific controllers/actions (e.g., <code>[Authorize]</code>, validation, response shaping)</li>



<li>You want cross-cutting logic reusable via attributes on select endpoints only</li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<div class="noteBlock"><p>Interview-ready summary</p><p>Middleware operates at the raw ASP.NET Core HTTP pipeline level and runs for every request, with no knowledge of MVC concepts like actions or model binding. Filters run inside the MVC pipeline — specifically inside the endpoint-execution middleware — and have five types (Authorization, Resource, Action, Exception, Result) that hook into specific stages of action execution, giving them access to richer context like action arguments and results. Use middleware for cross-cutting, framework-agnostic concerns; use filters when you need MVC-specific context or want behavior scoped to particular controllers/actions.</p></div>
</blockquote>



<p><b>18. What is the purpose of Invoke() / InvokeAsync() ?</b></p>
<p>This is the method that does the actual work of the middleware — it&#8217;s the piece the framework calls for every incoming request.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class CustomMiddleware
{
    private readonly RequestDelegate _next;

    public CustomMiddleware(RequestDelegate next)
    {
        _next = next; // stored once, at startup
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 1. Logic before passing control forward
        // 2. Call the next component in the pipeline
        await _next(context);
        // 3. Logic after the rest of the pipeline completes
    }
}
</pre></div>


<p class="wp-block-paragraph">InvokeAsync (or Invoke) is:</p>



<ol class="wp-block-list">
<li><strong>The per-request entry point</strong> — called by the ASP.NET Core runtime for every HTTP request that reaches this middleware.</li>



<li><strong>Where you receive <code>HttpContext</code></strong> — giving access to the request, response, user, services, etc., for <em>this specific request</em>.</li>



<li><strong>Where you call <code>_next(context)</code></strong> — passing control to the next middleware in the pipeline. This is what actually chains everything together.</li>



<li><strong>Where before/after logic lives</strong> — code before <code>_next()</code> runs on the way &#8220;in&#8221;; code after <code>_next()</code> runs on the way &#8220;out&#8221; (response phase).</li>
</ol>



<h3>Why It&#8217;s Separate from the Constructor</h3>



<p class="wp-block-paragraph">This is a very common interview follow-up, so it&#8217;s worth being precise:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th></th><th>Constructor</th><th>InvokeAsync</th></tr></thead><tbody><tr><td><strong>Called</strong></td><td>Once, at app startup</td><td>Once <strong>per HTTP request</strong></td></tr><tr><td><strong>Purpose</strong></td><td>Capture <code>_next</code> and inject <strong>singleton</strong> dependencies</td><td>Do the actual per-request work; inject <strong>scoped/transient</strong> dependencies as method parameters</td></tr><tr><td><strong>DI behavior</strong></td><td>Constructor is only ever resolved once, so scoped services (like <code>DbContext</code>) would effectively become singletons if injected here — <strong>bug risk</strong></td><td>Method parameters are resolved fresh by DI <strong>per call</strong>, so scoped services are safe here</td></tr></tbody></table></div>



<pre class="wp-block-code"><code>public class AuditMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger&lt;AuditMiddleware&gt; _logger; // OK: singleton-safe

    public AuditMiddleware(RequestDelegate next, ILogger&lt;AuditMiddleware&gt; logger)
    {
        _next = next;
        _logger = logger;
    }

    // AppDbContext is scoped — injected here, resolved fresh each request
    public async Task InvokeAsync(HttpContext context, AppDbContext db)
    {
        var user = context.User.Identity?.Name;
        db.AuditLogs.Add(new AuditLog { User = user, Path = context.Request.Path });
        await db.SaveChangesAsync();

        await _next(context);
    }
}</code></pre>



<p><u>Invoke vs InvokeAsync</u></p>



<ul class="wp-block-list">
<li>Functionally identical — the framework uses reflection to find either name.</li>



<li><strong>Convention:</strong> use <code>InvokeAsync</code> if the method is asynchronous (returns <code>Task</code>), which is almost always the case since you&#8217;re typically awaiting <code>_next(context)</code> or I/O. <code>Invoke</code> (synchronous) is rare in practice.</li>
</ul>



<p><u>What Happens Internally:</u></p>



<p class="wp-block-paragraph">Under the hood, <code>UseMiddleware&lt;T&gt;()</code> uses reflection to build a <code>RequestDelegate</code> that:</p>



<ol class="wp-block-list">
<li>Instantiates your middleware class once (passing <code>next</code> + singleton services into the constructor).</li>



<li>Wraps a call to <code>InvokeAsync</code>, resolving any additional parameters from <code>HttpContext.RequestServices</code> (the per-request DI scope) each time it&#8217;s invoked.</li>
</ol>



<p>This is effectively how the whole middleware pipeline is just a chain of <code>RequestDelegate</code> (<code>Func&lt;HttpContext, Task&gt;</code>) instances, each closing over the &#8220;next&#8221; one.</p>
<p><b>19. What is short-circuiting in middleware ?</b></p>
<p>Short-circuiting is when a middleware component ends the pipeline early by not invoking next(), meaning subsequent middleware and the endpoint are never reached. It&#8217;s used intentionally for things like serving static files, rejecting unauthenticated requests, or returning cached responses — avoiding unnecessary work downstream. The middleware that short-circuits is responsible for writing a complete response itself, since nothing further in the pipeline will run.</p>



<pre class="wp-block-code"><code>app.Use(async (context, next) =&gt;
{
    if (!context.Request.Headers.ContainsKey("X-Api-Key"))
    {
        context.Response.StatusCode = 401;
        await context.Response.WriteAsync("API key missing");
        return; // next() is NOT called — pipeline short-circuits here
    }

    await next(context); // only reached if the check passes
});</code></pre>



<p>Everything registered after this middleware — including later middleware and the MVC/endpoint layer — is skipped entirely for this request.</p>
<p><u>Common Real-World Examples:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Middleware</th><th>Why it short-circuits</th></tr></thead><tbody><tr><td><b>Static Files</b> (<code>UseStaticFiles</code>)</td><td>If the requested path matches a file on disk, it serves the file and stops — no need to hit routing/MVC</td></tr><tr><td><b>Authentication/Authorization failures</b></td><td>Return 401/403 immediately rather than letting the request reach a controller</td></tr><tr><td><strong>Response caching</strong></td><td>If a valid cached response exists, return it directly, skip regenerating it</td></tr><tr><td><b>Rate limiting</b></td><td>Return 429 immediately if the client has exceeded their limit</td></tr><tr><td><b>Health check endpoints</b></td><td>Respond immediately without going through the full MVC pipeline</td></tr><tr><td><b>app.Run()</b></td><td>By definition, terminal middleware — it never calls <code>next</code> because there&#8217;s nothing to call</td></tr></tbody></table></div>



<p><b>20. How would you implement global exception handling middleware ?</b></p>
<p>This is the modern, framework-supported way — cleaner, testable, and supports multiple chained handlers.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger&lt;GlobalExceptionHandler&gt; _logger;

    public GlobalExceptionHandler(ILogger&lt;GlobalExceptionHandler&gt; logger)
    {
        _logger = logger;
    }

    public async ValueTask&lt;bool&gt; TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        _logger.LogError(exception, &quot;Unhandled exception&quot;);

        var (statusCode, title) = exception switch
        {
            KeyNotFoundException =&gt; (StatusCodes.Status404NotFound, &quot;Not Found&quot;),
            ValidationException  =&gt; (StatusCodes.Status400BadRequest, &quot;Validation Error&quot;),
            _ =&gt; (StatusCodes.Status500InternalServerError, &quot;Server Error&quot;)
        };

        httpContext.Response.StatusCode = statusCode;

        await httpContext.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = statusCode,
            Title = title,
            Detail = exception.Message,
            Instance = httpContext.TraceIdentifier
        }, cancellationToken);

        return true; // true = handled, stop looking for other handlers
    }
}
</pre></div>


<p><u>Registration in Program.cs:</u></p>



<pre class="wp-block-code"><code>builder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;();
builder.Services.AddProblemDetails(); // enables standard ProblemDetails responses

var app = builder.Build();

app.UseExceptionHandler(); // uses the registered IExceptionHandler(s)

app.UseHttpsRedirection();
app.UseRouting();
app.MapControllers();

app.Run();</code></pre>



<p class="wp-block-paragraph">You can register <strong>multiple handlers</strong> — they&#8217;re tried in order until one returns <code>true</code>:</p>



<pre class="wp-block-code"><code>builder.Services.AddExceptionHandler&lt;ValidationExceptionHandler&gt;();
builder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;(); // fallback/catch-all last</code></pre>



<div class="note">Key Design Considerations (common follow-ups)</div>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Concern</th><th>Guidance</th></tr></thead><tbody><tr><td><b>Placement</b></td><td>Must be the <b>first</b> middleware (or very close to it) so it wraps everything downstream</td></tr><tr><td><b>Don&#8217;t leak details</b></td><td>In production, avoid returning stack traces / internal exception messages to the client — log them server-side, return generic messages</td></tr><tr><td><b>Consistent response shape</b></td><td>Use <code>ProblemDetails</code> (RFC 7807) — ASP.NET Core has built-in support via <code>AddProblemDetails()</code></td></tr><tr><td><b>Status code mapping</b></td><td>Map exception types → HTTP status codes via a switch expression or a dictionary, rather than always returning 500</td></tr><tr><td><b>Logging</b></td><td>Always log the full exception (with stack trace) server-side, even though the client gets a sanitized message</td></tr><tr><td><b>Environment-specific behavior</b></td><td>Often combined with <code>app.UseDeveloperExceptionPage()</code> in <code>Development</code> and the custom handler in <code>Production</code>:</td></tr></tbody></table></div>



<p><b>21. How can middleware access request and response information ?</b></p>
<p>In ASP.NET Core, middleware doesn&#8217;t get separate req/res parameters. Everything is bundled into a single <span class="code">HttpContext</span> object that&#8217;s passed through the pipeline.</p>



<pre class="wp-block-code"><code>public class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // Access request
        var path = context.Request.Path;
        var method = context.Request.Method;
        var headers = context.Request.Headers;

        // Do something before calling the next middleware
        Console.WriteLine($"{method} {path}");

        await _next(context); // pass control to next middleware

        // Do something after downstream middleware has run
        Console.WriteLine($"Response status: {context.Response.StatusCode}");
    }
}</code></pre>



<p><b>Key pieces</b></p>
<p>a. HttpContext</p>
<ul>
<li>The single object that wraps everything about the current request/response cycle.</li>
<li><span class="code">context.Request</span> → <span class="code">HttpRequest</span> object (path, method, headers, query string, body stream, cookies, etc.)</li>
<li><span class="code">context.Response</span> → <span class="code">HttpResponse</span> object (status code, headers, body stream, cookies)</li>
<li>Also exposes <span class="code">context.User</span> (claims principal), <span class="code">context.Items</span> (per-request key/value bag for passing data between middleware), <span class="code">context.Connection</span>, etc.</li>
</ul>
<p>b. RequestDelegate _next</p>
<ul>
<li>Represents &#8220;the rest of the pipeline.&#8221; Calling <span class="code">await _next(context)</span> invokes the next middleware.</li>
<li>Because it&#8217;s <span class="code">await</span> ed, code after that call runs after the downstream pipeline completes — this is how you read/modify the response on the way back out (e.g., logging status codes, injecting headers based on the final response).</li>
</ul>
<p>c. Reading the request</p>



<pre class="wp-block-code"><code>   var token = context.Request.Headers&#91;"Authorization"];
   var body = await new StreamReader(context.Request.Body).ReadToEndAsync();</code></pre>



<p>d. Modifying the response</p>



<pre class="wp-block-code"><code>context.Response.StatusCode = 401;
context.Response.Headers&#91;"X-Custom"] = "value";
await context.Response.WriteAsync("Unauthorized");</code></pre>



<p>Note: once <code>context.Response.Body</code> has started being written to (headers flushed), you can no longer change the status code or headers — a common gotcha interviewers like to probe.</p>
<p>e. Short-circuiting</p>
<ul>
<li>A middleware can choose not to call <span class="code">_next(context)</span> at all, ending the pipeline early (e.g., auth failure, rate limiting, serving a cached response).</li>
</ul>
<p>f. Registration order matters</p>
<ul>
<li>Middleware is wired up in <span class="term">Program.cs/Startup.cs via app.Use&#8230;()</span> calls, and executes in that exact order on the way in, then unwinds in reverse order on the way out (it&#8217;s effectively a nested/recursive chain, not a flat list).</li>
</ul>
<p>g. Alternative styles</p>
<ul>
<li>Besides the class-based convention above, ASP.NET Core also supports inline delegate middleware:</li>
</ul>



<pre class="wp-block-code"><code>app.Use(async (context, next) =&gt;
{
    // before
    await next();
    // after
});</code></pre>



<ul>
<li>And <span class="code">IMiddleware</span> (factory-based) for DI-friendly, per-request instantiated middleware.</li>
</ul>
<div class="noteBlock"><p>Interview-worthy comparison point:</p>
<p>ASP.NET Core uses one context object with two-way (in/out) access and an awaitable pipeline, so a single middleware naturally handles both the &#8220;before&#8221; and &#8220;after&#8221; phases in one method — no separate <u>req/res</u> params.</p>
</div>
<p><b>22. Why Exception-Handling Middleware Goes Early in the Pipeline ?</b></p>
<p>The key reason: middleware can only catch exceptions thrown by components that run after it in the pipeline.</p>
<p>ASP.NET Core middleware forms a nested call chain via <code>await _next(context)</code>. If middleware A calls middleware B which calls middleware C, and C throws, that exception propagates back up through B and then A — but only if A and B are &#8220;wrapping&#8221; C, i.e., positioned before it in registration order.</p>



<pre class="wp-block-code"><code>app.UseExceptionHandler("/Error");   // registered early
app.UseHsts();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(...);               // exceptions can occur here</code></pre>



<p class="wp-block-paragraph">Because <code>UseExceptionHandler</code> is registered near the top, it wraps everything that comes after it (routing, auth, endpoint execution, etc.) in a try/catch. When any of those downstream components throw, the exception bubbles back up through the call stack and lands inside the exception-handling middleware&#8217;s catch block.</p>



<div class="note">What happens if you place it late</div>
<p>If you register <span class="code">UseExceptionHandler</span> after other middleware — say, after <span class="code">UseRouting</span> or <span class="code">UseEndpoints</span> — then:</p>
<ul>
<li>Any exception thrown by middleware before it (routing, auth, custom middleware, etc.) will never reach it, because the exception handler isn&#8217;t wrapping those components.</li>
<li>The exception would instead propagate all the way up to the server (Kestrel) or the built-in developer exception page (if enabled), resulting in an unhandled 500 error with no custom error page/logging — defeating the purpose of having centralized error handling.</li>
</ul>
<div class="noteBlock"><u>One-line interview answer</u>: Because middleware in ASP.NET Core only catches exceptions from components later in the pipeline (due to how the nested next() delegate chain unwinds), exception-handling middleware must be registered early so it wraps — and can catch errors from — everything that runs after it.</div>



<h2>Routing Interview Questions</h2>
<p>Routing is one of the highest-yield topics interviewers probe because it sits at the intersection of &#8220;how does a request become a method call&#8221; — a question that tests whether you understand the framework&#8217;s core mechanics rather than just memorized syntax. Here&#8217;s how to think about its importance and what&#8217;s commonly asked.</p>
<p><u>Why it&#8217;s a big deal conceptually:</u></p>
<p>Routing is the mechanism that maps an incoming URL + HTTP method to a specific piece of executable code (an endpoint — typically a controller action or minimal API delegate). Without it, the framework has no way of knowing what code should handle <span class="code">GET /api/products/5</span> vs <span class="code">POST /api/products</span>. It&#8217;s the bridge between the raw HTTP request and your application logic.</p>
<p><u>Interviewers care about it because:</u></p>
<ul>
<li>It reveals whether you understand the request pipeline as a whole (routing is a middleware itself — <span class="term">UseRouting()/UseEndpoints()</span> or the newer minimal hosting model).</li>
<li>It&#8217;s where real-world bugs happen — ambiguous routes, wrong HTTP verb handling, route ordering issues, constraint mismatches.</li>
<li>It connects to API design — RESTful conventions, versioning, attribute vs conventional routing.</li>
</ul>
<p><b>23. What is routing in ASP.NET Core ? What is attribute routing ? What is conventional routing ?</b></p>
<p>Routing is the mechanism that matches an incoming HTTP request to an executable endpoint (a controller action, Razor Page, or minimal API delegate) based on the request&#8217;s URL and HTTP method, and extracts route values (like IDs) from the URL to make them available to that endpoint.</p>
<p>In other words: when a request comes in like <span class="code">GET /products/5</span>, routing is what decides which method in your code should handle it, and it also extracts 5 as a value (e.g., id = 5) that gets passed into that method.</p>
<p>routing is implemented as endpoint routing, split into two middleware calls:</p>



<pre class="wp-block-code"><code>app.UseRouting();      // matches the request to an endpoint
// ... other middleware (auth, etc.) can inspect the matched endpoint here ...
app.UseEndpoints(endpoints =&gt;
{
    endpoints.MapControllers(); // executes the matched endpoint
});</code></pre>



<p>In minimal hosting, this is often implicit — you just call things like <span class="code">app.MapGet(&#8230;)</span> or <span class="code">app.MapControllers()</span> directly, and the framework wires up routing/endpoints for you.</p>
<p><b>There are two ways to define routes: conventional routing and attribute routing.</b></p>
<h3>Conventional Routing</h3>
<p>Conventional routing defines route patterns centrally, usually once, and lets those patterns apply broadly across controllers based on naming conventions (controller name, action name).</p>



<pre class="wp-block-code"><code>app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");</code></pre>



<p><u>How it works:</u></p>
<ul>
<li>The URL is matched against the pattern&#8217;s placeholders: <span class="code">{controller}</span>, <span class="code">{action}</span>, <span class="code">{id}</span>.
<span class="code">{controller=Home}</span> and <span class="code">{action=Index}</span> provide default values — if the URL segment is missing, it defaults to Home/Index.</li>
<li><span class="code">{id?}</span> marks id as optional.</li>
<li>So <span class="code">/Products/Details/5</span> maps to <span class="code">ProductsController.Details(int id)</span> with <span class="code">id = 5</span>. A request to just <span class="code">/</span> maps to <span class="code">HomeController.Index()</span> via the defaults.</li>
</ul>
<p><u>Characteristics:</u></p>
<ul>
<li>One central place to define URL structure/shape for a whole app.</li>
<li>Relies on a consistent naming convention across controllers — if you deviate, the &#8220;convention&#8221; breaks down and you need attribute routing anyway.</li>
<li>More common in traditional MVC apps (server-rendered views) with predictable, uniform URL patterns.</li>
<li>Less precise control per-action — harder to express things like versioned APIs or unconventional URL shapes cleanly.</li>
</ul>
<h3>Attribute Routing</h3>
<p>Attribute routing defines routes directly on controllers and actions using attributes, giving fine-grained, explicit control over each endpoint&#8217;s URL.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;Route(&quot;api/&#x5B;controller]&quot;)]
public class ProductsController : ControllerBase
{
    &#x5B;HttpGet]                  // GET api/products
    public IActionResult GetAll() { ... }

    &#x5B;HttpGet(&quot;{id:int}&quot;)]      // GET api/products/5
    public IActionResult Get(int id) { ... }

    &#x5B;HttpPost]                 // POST api/products
    public IActionResult Create(Product product) { ... }
}
</pre></div>


<p><u>How it works:</u></p>
<ul>
<li><span class="code">[Route(&#8220;api/[controller]&#8221;)]</span> on the class sets a base template; <span class="code">[controller]</span> is a token that resolves to the controller&#8217;s name (Products).</li>
<li><span class="code">[HttpGet]</span>, <span class="code">[HttpPost]</span>, <span class="code">[HttpPut]</span>, <span class="code">[HttpDelete]</span> attributes on individual actions both specify the HTTP verb and can append to/override the route template.</li>
<li>Route constraints (<span class="code">{id:int}</span>), optional segments, and custom templates can be set per-action with full precision.</li>
</ul>
<p><u>Characteristics:</u></p>
<ul>
<li>Explicit and self-documenting — you can see the exact route right next to the action that handles it.</li>
<li>Standard for Web APIs, since REST conventions often need precise control (e.g., <span class="term">GET /api/products/{id}</span> vs <span class="term">GET /api/products/{id}/reviews)</span>.</li>
<li>Doesn&#8217;t rely on naming convention magic — each route is declared exactly as intended.</li>
<li>Easier to manage in large APIs with many endpoints that don&#8217;t follow a single uniform pattern.</li>
</ul>
<p><b>24. What is the difference between [Route], [HttpGet], [HttpPost], etc ?</b></p>
<p><span class="code">[Route]</span> defines the URL pattern/template that maps to an action or controller. It doesn&#8217;t say anything about which HTTP verb (GET, POST, etc.) is allowed — it just sets up the path.</p>



<pre class="wp-block-code"><code>&#91;Route("api/&#91;controller]")]
public class ProductsController : ControllerBase
{
    &#91;Route("{id}")]
    public IActionResult GetById(int id) { ... }
}</code></pre>



<p>By default, <span class="code">[Route]</span> alone (without an HTTP verb attribute) responds to any HTTP method unless you explicitly restrict it.</p>
<h3>[HttpGet], [HttpPost], [HttpPut], [HttpDelete], [HttpPatch]</h3>
<p>These are HTTP verb attributes — they restrict an action to a specific HTTP method, and they can also optionally carry a route template.</p>



<pre class="wp-block-code"><code>&#91;HttpGet]
public IActionResult GetAll() { ... }

&#91;HttpGet("{id}")]
public IActionResult GetById(int id) { ... }

&#91;HttpPost]
public IActionResult Create(&#91;FromBody] Product product) { ... }</code></pre>



<p><u>Key Differences:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered">
<thead><tr class="table-primary"><th>Aspect</th><th>[Route]</th><th>[HttpGet]/[HttpPost]/etc.</th></tr></thead><tbody><tr><td>Purpose</td><td>Defines URL template</td><td>Restricts HTTP verb (+ optional URL template)</td></tr><tr><td>HTTP verb restriction</td><td>None (allows all verbs unless combined)</td><td>Restricts to one specific verb</td></tr><tr><td>Can carry a route?</td><td>Yes</td><td>Yes (optional parameter)</td></tr><tr><td>Typical use</td><td>On controller, to set a base path</td><td>On actions, for verb + route together</td></tr></tbody></table></div>
<p><u>How they typically combine:</u></p>



<pre class="wp-block-code"><code>&#91;Route("api/&#91;controller]")]
public class ProductsController : ControllerBase
{
    &#91;HttpGet]                     // GET api/products
    public IActionResult GetAll() { ... }

    &#91;HttpGet("{id}")]             // GET api/products/5
    public IActionResult GetById(int id) { ... }

    &#91;HttpPost]                    // POST api/products
    public IActionResult Create(&#91;FromBody] Product p) { ... }

    &#91;HttpPut("{id}")]             // PUT api/products/5
    public IActionResult Update(int id, &#91;FromBody] Product p) { ... }

    &#91;HttpDelete("{id}")]          // DELETE api/products/5
    public IActionResult Delete(int id) { ... }
}</code></pre>



<ul>
<li><code>[Route]</code> on the controller sets the base path (<code>api/products</code>).</li>
<li>Each <code>[Http*]</code> attribute appends its own segment and locks the action to that verb.</li>
</ul>
<p><u>Why not just use <span class="code">[Route]</span> everywhere? You could write:</u></p>



<pre class="wp-block-code"><code>&#91;Route("api/products")]
&#91;AcceptVerbs("GET")]
public IActionResult GetAll() { ... }</code></pre>



<p>But <span class="code">[HttpGet]</span>, <span class="code">[HttpPost]</span>, etc. are more concise, self-documenting, and are the idiomatic convention in ASP.NET Core Web API — they make it immediately clear (to readers and to Swagger/OpenAPI tooling) what verb an action responds to.</p>
<div class="starBlock">
<p>Interview one-liner:</p>
<p>[Route] defines the URL pattern an action responds to, while [HttpGet], [HttpPost], etc. restrict an action to a specific HTTP verb — and can also define a route themselves. In practice, they&#8217;re used together: [Route] at the controller level for a common prefix, and verb attributes at the action level for both routing and verb constraint.</p>
</div>
<p><b>25. What are route constraints ? Explain {id:int}. How do you create a custom route constraint ?</b></p>
<p>Route constraints restrict whether a route matches based on the value of a route parameter — not just its presence, but its type, format, or range. They act as a filter so a URL segment is only accepted if it satisfies a rule.</p>
<p>Without a constraint, <span class="code">{id}</span> matches any string:</p>



<pre class="wp-block-code"><code>GET api/products/5       ✅ matches
GET api/products/abc     ✅ also matches (id = "abc")</code></pre>



<p>With a constraint, you can force &#8220;id&#8221; to be numeric only, so &#8220;abc&#8221; won&#8217;t match that route at all — it&#8217;ll either 404 or fall through to another matching route/action.</p>
<p><u>Syntax:</u></p>
<p>Constraints are applied using a colon after the parameter name:</p>



<pre class="wp-block-code"><code>{parameterName:constraint}</code></pre>



<p><u>Common Built-in Constraints:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Constraint</th><th>Description</th><th>Example</th></tr></thead><tbody><tr><td><code>int</code></td><td>Matches an integer</td><td><code>{id:int}</code></td></tr><tr><td><code>bool</code></td><td>Matches true/false</td><td><code>{active:bool}</code></td></tr><tr><td><code>datetime</code></td><td>Matches a valid DateTime</td><td><code>{date:datetime}</code></td></tr><tr><td><code>decimal</code></td><td>Matches a decimal</td><td><code>{price:decimal}</code></td></tr><tr><td><code>double</code> / <code>float</code></td><td>Matches double/float</td><td><code>{value:double}</code></td></tr><tr><td><code>long</code></td><td>Matches a long</td><td><code>{id:long}</code></td></tr><tr><td><code>guid</code></td><td>Matches a GUID</td><td><code>{id:guid}</code></td></tr><tr><td><code>alpha</code></td><td>Alphabetic characters only</td><td><code>{name:alpha}</code></td></tr><tr><td><code>minlength(n)</code></td><td>Minimum string length</td><td><code>{name:minlength(3)}</code></td></tr><tr><td><code>maxlength(n)</code></td><td>Maximum string length</td><td><code>{name:maxlength(10)}</code></td></tr><tr><td><code>length(n)</code> / <code>length(min,max)</code></td><td>Exact or range length</td><td><code>{code:length(5,10)}</code></td></tr><tr><td><code>min(n)</code> / <code>max(n)</code></td><td>Numeric min/max value</td><td><code>{id:min(1)}</code></td></tr><tr><td><code>range(min,max)</code></td><td>Value within a range</td><td><code>{age:range(18,65)}</code></td></tr><tr><td><code>regex(pattern)</code></td><td>Matches a regex pattern</td><td><code>{code:regex(^\d{{4}}$)}</code></td></tr><tr><td><code>required</code></td><td>Value must be provided</td><td><code>{name:required}</code></td></tr></tbody></table></div>
<p><u>Example:</u></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;Route(&quot;api/&#x5B;controller]&quot;)]
public class ProductsController : ControllerBase
{
    // Matches only if id is an integer
    &#x5B;HttpGet(&quot;{id:int}&quot;)]
    public IActionResult GetById(int id) { ... }

    // Matches only if id is a GUID
    &#x5B;HttpGet(&quot;{id:guid}&quot;)]
    public IActionResult GetByGuid(Guid id) { ... }

    // Combine multiple constraints
    &#x5B;HttpGet(&quot;{id:int:min(1)}&quot;)]
    public IActionResult GetPositive(int id) { ... }
}
</pre></div>


<p><u>Optional parameters with constraints:</u></p>
<p>You can combine a constraint with <u>?</u> to make it optional:</p>



<pre class="wp-block-code"><code>&#91;HttpGet("{id:int?}")]
public IActionResult Get(int? id) { ... }</code></pre>



<h3>{id:int} Explained</h3>
<p>{id:int} is a route parameter named id constrained to match only integer values — if the URL segment can&#8217;t be parsed as an int, the route doesn&#8217;t match, which helps with routing disambiguation and early filtering before the action even executes.</p>
<h3>Creating a Custom Route Constraint in ASP.NET Core</h3>
<p>To create a custom route constraint, you implement the <span class="code">IRouteConstraint</span> interface and register it in the routing options. This lets you define matching logic beyond the built-in constraints (<i>int, guid, alpha</i>, etc.).</p>
<p><u>Step 1: Implement IRouteConstraint:</u></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class EvenNumberConstraint : IRouteConstraint
{
    public bool Match(
        HttpContext httpContext,
        IRouter route,
        string routeKey,
        RouteValueDictionary values,
        RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out var value) &&amp; value != null)
        {
            if (int.TryParse(value.ToString(), out int number))
            {
                return number % 2 == 0;
            }
        }
        return false;
    }
}
</pre></div>


<p>Key points about the Match method:</p>
<ul>
<li>Returns true if the value satisfies the constraint (route matches), false otherwise.</li>
<li>routeKey is the parameter name (e.g., &#8220;id&#8221;).
values is the dictionary of route values for the current match attempt.</li>
<li>routeDirection tells you whether this is being evaluated for an incoming request (IncomingRequest) or for URL generation (UrlGeneration) — useful if constraint logic should differ.</li>
</ul>
<p><u>Step 2: Register the constraint</u></p>
<p>In Program.cs:</p>



<pre class="wp-block-code"><code>builder.Services.Configure&lt;RouteOptions&gt;(options =&gt;
{
    options.ConstraintMap.Add("even", typeof(EvenNumberConstraint));
});</code></pre>



<p>This maps the string key &#8220;even&#8221; to your constraint class — that key is what you&#8217;ll use in route templates.</p>
<p><u>Step 3: Use it in a route</u></p>



<pre class="wp-block-code"><code>&#91;HttpGet("{id:even}")]
public IActionResult GetEven(int id)
{
    return Ok($"{id} is even");
}</code></pre>



<pre class="wp-block-code"><code>GET /api/products/4	✅ Yes
GET /api/products/5	❌ No (odd)
GET /api/products/abc	❌ No (not even a number)</code></pre>



<p><u>Important considerations:</u></p>
<ul>
<li>Constraints run on every routing attempt — keep the logic fast and lightweight. Avoid database calls or expensive operations inside Match.</li>
<li>Constraints affect routing, not validation — a failed match usually results in a 404 (or falls through to another matching route), not a meaningful validation error. Don&#8217;t use custom constraints as a substitute for model validation.</li>
<li>Constraints can be chained — e.g. {id:int:even} if you want to combine built-in and custom constraints.</li>
<li>Alternative approach: IActionConstraint — if what you actually need is to select between overloaded actions (rather than restrict URL matching), IActionConstraint may be more appropriate; it&#8217;s evaluated later in the pipeline, after routing has matched a template, and can be closer to per-action selection logic.</li>
</ul>
<p><b>26. What is route grouping ? What is MapGroup() in Minimal APIs ? How can you apply authorization to an entire route group ?</b></p>
<p>Route grouping lets you organize a set of related endpoints under a common prefix and shared configuration (metadata, filters, authorization, etc.) — instead of repeating the same settings on every single endpoint.</p>
<p>It&#8217;s the Minimal API equivalent of what [Route(&#8220;api/[controller]&#8221;)] + controller-level attributes give you in MVC/Web API — a way to apply cross-cutting concerns (prefix, auth, filters, tags, versioning) once, at the group level, rather than per-endpoint.</p>
<h3>MapGroup()</h3>
<p>MapGroup() is the method (introduced in .NET 7) used to create a route group in Minimal APIs. It returns a RouteGroupBuilder, which itself implements IEndpointRouteBuilder — so you can chain .MapGet(), .MapPost(), etc. on it just like you would on the app directly, and any group-level configuration cascades down to all endpoints in the group.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var app = builder.Build();

var products = app.MapGroup(&quot;api/products&quot;);

products.MapGet(&quot;/&quot;, GetAllProducts);
products.MapGet(&quot;/{id:int}&quot;, GetProductById);
products.MapPost(&quot;/&quot;, CreateProduct);
products.MapPut(&quot;/{id:int}&quot;, UpdateProduct);
products.MapDelete(&quot;/{id:int}&quot;, DeleteProduct);

app.Run();
</pre></div>


<p class="wp-block-paragraph">Here, every endpoint automatically gets the <code>api/products</code> prefix:</p>



<ul class="wp-block-list">
<li><code>GET api/products/</code></li>



<li><code>GET api/products/{id}</code></li>



<li><code>POST api/products/</code></li>
</ul>



<p><u>Why use it (benefits):</u></p>



<ol class="wp-block-list">
<li><strong>Shared prefix</strong> — avoid repeating <code>"api/products/..."</code> on every <code>Map*</code> call.</li>



<li><strong>Shared metadata/filters</strong> — apply <code>.WithTags()</code>, <code>.WithOpenApi()</code>, <code>.AddEndpointFilter()</code>, <code>.RequireAuthorization()</code>, <code>.RequireCors()</code>, <code>.RequireRateLimiting()</code>, etc. <strong>once</strong> on the group, and it applies to all endpoints inside it.</li>



<li><strong>Nested groups</strong> — groups can be nested for hierarchical organization:</li>
</ol>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
   var api = app.MapGroup(&quot;api&quot;);
   var v1 = api.MapGroup(&quot;v1&quot;);
   var products = v1.MapGroup(&quot;products&quot;); // final prefix: api/v1/products
</pre></div>


<ol start="4" class="wp-block-list">
<li><strong>Cleaner Program.cs</strong> — especially useful for organizing large Minimal API apps without needing full MVC controllers.</li>



<li><strong>Can be extracted into extension methods</strong> for modular endpoint registration (a common pattern replacing &#8220;one giant Program.cs&#8221;).</li>
</ol>



<h3>Applying Authorization to an Entire Route Group</h3>



<p class="wp-block-paragraph">Use <code>.RequireAuthorization()</code> on the group returned by <code>MapGroup()</code> — it cascades to every endpoint registered on that group.</p>



<pre class="wp-block-code"><code>var products = app.MapGroup("api/products")
                   .RequireAuthorization(); // applies to ALL endpoints below

products.MapGet("/", GetAllProducts);
products.MapPost("/", CreateProduct);   // also requires auth
products.MapDelete("/{id:int}", DeleteProduct); // also requires auth</code></pre>



<p class="wp-block-paragraph">You can also target a specific <strong>policy</strong> or <strong>role</strong>:</p>



<pre class="wp-block-code"><code>var admin = app.MapGroup("api/admin")
                .RequireAuthorization("AdminOnly"); // named policy

var orders = app.MapGroup("api/orders")
                 .RequireAuthorization(policy =&gt; policy.RequireRole("Manager"));</code></pre>



<p><u>Overriding at the endpoint level:</u></p>



<p class="wp-block-paragraph">Metadata applied at the group level can be <strong>overridden per-endpoint</strong> if needed. For example, to make one endpoint in an otherwise-protected group public:</p>



<pre class="wp-block-code"><code>var products = app.MapGroup("api/products")
                   .RequireAuthorization();

products.MapGet("/", GetAllProducts);

products.MapGet("/public-info", GetPublicInfo)
        .AllowAnonymous(); // overrides the group's RequireAuthorization</code></pre>



<p>This works because endpoint metadata is combined/overridden based on specificity — more specific (endpoint-level) metadata takes precedence over group-level metadata.</p>
<h2>ASP.NET Core Web API Interview Questions</h2>
<p>Why prepare specifically around ASP.NET Core Web API interview questions:</p>
<p><b>It&#8217;s usually the actual job, not a formality</b> &#8211; For most .NET backend roles, Web API work is the day-to-day — building endpoints, wiring up auth, handling data access, structuring services. Interviewers ask these questions because they map almost 1:1 to what you&#8217;ll be doing in week one, unlike, say, algorithmic trivia that rarely shows up in daily work.</p>
<p><b>It&#8217;s a fast way to filter &#8220;used it&#8221; from &#8220;understands it&#8221;</b> &#8211; Web API has enough moving parts (routing, model binding, filters, middleware, DI, auth) that surface-level familiarity breaks down quickly under follow-up questions. A structured interview on this topic lets an interviewer distinguish, in 20–30 minutes, someone who&#8217;s copy-pasted from tutorials from someone who&#8217;s actually debugged production issues.</p>
<p><b>It reveals whether you understand the pipeline, not just isolated features</b> &#8211; Almost every ASP.NET Core interview thread (like the one we just went through) is designed to walk you from a narrow starting point ([Route] vs [HttpGet]) into the broader request lifecycle. Preparing deliberately means you&#8217;re not just memorizing 50 disconnected facts — you&#8217;re building a mental model of how a request flows from routing → model binding → filters → action → response. That mental model is reusable and lets you improvise on questions you&#8217;ve never seen before.</p>
<p><b>Depth matters more than breadth at senior levels</b> &#8211; Junior interviews often stop at &#8220;what is X.&#8221; Mid/senior interviews probe trade-offs and failure modes — e.g., &#8220;constraints don&#8217;t validate, they just affect matching, so a mismatch gives you a 404 not a helpful error.&#8221; If you haven&#8217;t prepared with that framing in mind, you&#8217;ll answer correctly but sound junior anyway.</p>
<p><b>Practical outcome: it directly affects your interview performance</b> &#8211; Concretely, structured prep helps you:</p>
<ul>
<li>Answer confidently without long pauses (fluency signals real experience)</li>
<li>Anticipate the natural follow-up question (interviewers often chain, like we did: [Route] → constraints → custom constraints → groups)</li>
<li>Avoid contradicting yourself when asked &#8220;why not just do X instead&#8221; (a very common interview technique)</li>
<li>Talk about real-world gotchas unprompted, which is the strongest signal of hands-on experience</li>
</ul>
<div class="starBlock"><p>I have written complete series on ASP.NET Core Web API. It contains 5 tutorials to master this area:</p>
<ul>
<li>1. <a target="_blank" href="https://www.yogihosting.com/aspnet-core-api-controllers/" rel="noopener noreferrer">API Controllers – Creating API in ASP.NET Core</a></li>
<li>2. <a target="_blank" href="https://www.yogihosting.com/aspnet-core-consume-api/" rel="noopener noreferrer">How to Call Web API in ASP.NET Core</a></li>
<li>3. <a target="_blank" href="https://www.yogihosting.com/aspnet-core-consume-api-jquery/" rel="noopener noreferrer">How to Call Web API from jQuery</a></li>
<li>4. <a href="https://www.yogihosting.com/aspnet-core-web-api-javascript/" target="_blank" rel="noopener noreferrer">Call Web API from JavaScript with XMLHttpRequest (XHR)</a></li>
</ul>
</div>
<p><b>27. What is ASP.NET Core Web API ? What is the difference between MVC and Web API ?</b></p>
<p>ASP.NET Core Web API is a framework built on top of ASP.NET Core for building HTTP-based services (RESTful APIs) that can be consumed by a wide range of clients — browsers, mobile apps, desktop apps, IoT devices, or other servers. Instead of returning HTML views, it returns data — typically in JSON (or XML) — over HTTP.</p>
<p><u>Key points to mention in an interview:</u></p>
<ul>
<li>It&#8217;s cross-platform (runs on Windows, Linux, macOS) since it&#8217;s built on .NET Core.
It&#8217;s designed around HTTP verbs — GET, POST, PUT, DELETE, PATCH — mapped to CRUD operations.</li>
<li>Controllers typically inherit from <span class="code">ControllerBase</span> (not <span class="code">Controller</span>), since they don&#8217;t need view-rendering capabilities.</li>
<li>It supports content negotiation (returning JSON or XML based on the <span class="code">Accept</span> header), model binding, model validation, filters, middleware, dependency injection, etc. — same underlying pipeline as ASP.NET Core MVC.</li>
<li>In modern ASP.NET Core (post 3.0), MVC and Web API have been unified into a single framework — there&#8217;s no separate &#8220;Web API&#8221; package anymore. You just build a Web API project using the same <span class="code">Microsoft.AspNetCore.Mvc</span> namespace.</li>
</ul>
<p><u>Example minimal controller:</u></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;ApiController]
&#x5B;Route(&quot;api/&#x5B;controller]&quot;)]
public class ProductsController : ControllerBase
{
    &#x5B;HttpGet]
    public IActionResult GetAll() =&gt; Ok(products);

    &#x5B;HttpGet(&quot;{id}&quot;)]
    public IActionResult GetById(int id) =&gt; Ok(product);

    &#x5B;HttpPost]
    public IActionResult Create(Product product) =&gt; CreatedAtAction(...);
}
</pre></div>


<h3>Difference between MVC and Web API</h3>
<p>This is a bit of a &#8220;trick&#8221; interview question in the ASP.NET Core world, because historically (in classic ASP.NET / pre-Core), MVC and Web API were separate frameworks. In ASP.NET Core, they&#8217;ve been merged into one framework (ASP.NET Core MVC), so technically there is no separate &#8220;Web API framework&#8221; anymore — it&#8217;s all part of the same pipeline.</p>
<p>That said, interviewers usually want you to explain the conceptual difference in purpose/usage:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Aspect</th><th>MVC (Controller)</th><th>Web API (ApiController)</th></tr></thead><tbody><tr><td><strong>Purpose</strong></td><td>Serves web pages (HTML views) to browsers</td><td>Serves data (usually JSON/XML) to any client</td></tr><tr><td><strong>Base class</strong></td><td><code>Controller</code> (has view support)</td><td><code>ControllerBase</code> (no view support, lighter)</td></tr><tr><td><strong>Return type</strong></td><td>Typically returns <code>View()</code> / <code>ViewResult</code></td><td>Typically returns <code>IActionResult</code> / <code>ActionResult&lt;T&gt;</code> / data objects</td></tr><tr><td><strong>Consumers</strong></td><td>Browsers rendering UI</td><td>SPAs (Angular/React), mobile apps, other services</td></tr><tr><td><strong>Attribute</strong></td><td>No <code>[ApiController]</code> needed</td><td>Decorated with <code>[ApiController]</code> for API-specific behaviors (automatic model validation, binding source inference, problem-details responses)</td></tr><tr><td><strong>Routing style</strong></td><td>Convention-based routing common (<code>{controller}/{action}/{id}</code>)</td><td>Attribute routing common (<code>[Route("api/[controller]")]</code>)</td></tr><tr><td><strong>Content negotiation</strong></td><td>Usually renders Razor views</td><td>Relies on formatters (JSON/XML) based on <code>Accept</code> header</td></tr></tbody></table></div>



<p><b>28. What is ControllerBase ? What is the difference between Controller and ControllerBase ? What does [ApiController] do ?</b></p>
<p><code>ControllerBase</code> is the base class for building API controllers in ASP.NET Core. It lives in the Microsoft.AspNetCore.Mvc namespace and provides all the core functionality needed to handle HTTP requests and produce responses — without any support for rendering views (HTML).</p>
<p>It gives you access to things like:</p>
<ul>
<li>HttpContext, Request, Response</li>
<li>ModelState (for validation)</li>
<li>Helper methods for building responses: Ok(), NotFound(), BadRequest(), CreatedAtAction(), NoContent(), StatusCode(), etc.</li>
<li>User (for accessing the authenticated user/claims)</li>
<li>Url (IUrlHelper for generating links)</li>
</ul>
<p><u>Example:</u></p>



<pre class="wp-block-code"><code>&#91;ApiController]
&#91;Route("api/&#91;controller]")]
public class ProductsController : ControllerBase
{
    &#91;HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        var product = _repo.Find(id);
        if (product == null)
            return NotFound();

        return Ok(product);
    }
}</code></pre>



<h3>Difference between Controller and ControllerBase</h3>
<p><code>Controller</code> inherits from <code>ControllerBase</code> and adds view-related functionality on top of it.</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Aspect</th><th><code>ControllerBase</code></th><th><code>Controller</code></th></tr></thead><tbody><tr><td><strong>Inheritance</strong></td><td>Base class</td><td>Inherits from <code>ControllerBase</code></td></tr><tr><td><strong>View support</strong></td><td>❌ No view rendering</td><td>✅ Supports <code>View()</code>, <code>PartialView()</code>, Razor views</td></tr><tr><td><strong>Used for</strong></td><td>Web APIs (returning data — JSON/XML)</td><td>MVC apps (returning HTML views)</td></tr><tr><td><strong>Extra methods</strong></td><td>Only data/response helpers (<code>Ok</code>, <code>NotFound</code>, etc.)</td><td>All of the above <strong>plus</strong> <code>View()</code>, <code>ViewBag</code>, <code>ViewData</code>, <code>TempData</code></td></tr><tr><td><strong>Typical return type</strong></td><td><code>IActionResult</code>, <code>ActionResult&lt;T&gt;</code>, data</td><td><code>ViewResult</code>, <code>IActionResult</code></td></tr></tbody></table></div>



<h3>What does [ApiController] do ?</h3>
<p><code>[ApiController]</code> is an attribute you put on a controller class to opt into a set of API-specific conventions and behaviors that make building REST APIs easier and more consistent. It&#8217;s typically combined with <code>ControllerBase</code>.</p>
<p>Specifically, it enables:</p>



<p class="wp-block-paragraph">a. <strong>Automatic HTTP 400 responses on model validation errors</strong></p>



<ul class="wp-block-list">
<li>If <code>ModelState.IsValid</code> is false, the framework automatically returns a <code>400 Bad Request</code> with a <code>ProblemDetails</code> response — you don&#8217;t need to manually check <code>ModelState.IsValid</code> in every action.</li>
</ul>



<p class="wp-block-paragraph">b. <strong>Attribute routing requirement</strong></p>



<ul class="wp-block-list">
<li>Forces the use of attribute-based routing (<code>[Route]</code>, <code>[HttpGet]</code>, etc.) instead of conventional routing — makes route definitions explicit and required.</li>
</ul>



<p class="wp-block-paragraph">c. <strong>Binding source parameter inference</strong></p>



<ul class="wp-block-list">
<li>Automatically infers where action parameters come from (<code>[FromBody]</code>, <code>[FromRoute]</code>, <code>[FromQuery]</code>) based on parameter type/complexity, so you often don&#8217;t need to specify them manually.</li>



<li>Example: complex types default to <code>[FromBody]</code>, simple types default to <code>[FromQuery]</code>/<code>[FromRoute]</code>.</li>
</ul>



<p class="wp-block-paragraph">d. <strong>Multipart/form-data inference for <code>IFormFile</code> parameters</strong></p>



<ul class="wp-block-list">
<li>Automatically recognizes file upload parameters.</li>
</ul>



<p class="wp-block-paragraph">e. <strong>Problem details for error responses</strong></p>



<ul class="wp-block-list">
<li>Standardizes error responses using the <code>ProblemDetails</code> format (RFC 7807), improving API consistency.</li>
</ul>



<p><b>29. What happens when model validation fails with [ApiController] ?</b></p>
<p>When a controller is decorated with [ApiController], ASP.NET Core automatically handles invalid model state for you — you don&#8217;t need to write if (!ModelState.IsValid) return BadRequest(ModelState); manually in every action.</p>
<p><u>What happens under the hood:</u></p>
<ul>
<li>Model binding runs first (mapping request data — body, route, query — to your action parameters/model).</li>
<li>If binding produces validation errors (e.g., a required field is missing, a data annotation like [Required], [Range], [StringLength] fails), the framework sets ModelState.IsValid = false.</li>
<li>Before your action method even executes, a built-in filter — ModelStateInvalidFilter — runs as part of the action-invocation pipeline and short-circuits the request.</li>
<li>It automatically returns an HTTP 400 Bad Request response, formatted as a ValidationProblemDetails object (an extension of ProblemDetails, per RFC 7807), containing the validation errors.</li>
</ul>
<p><u>Example:</u></p>



<pre class="wp-block-code"><code>public class ProductDto
{
    &#91;Required]
    public string Name { get; set; }

    &#91;Range(1, 1000)]
    public decimal Price { get; set; }
}

&#91;ApiController]
&#91;Route("api/&#91;controller]")]
public class ProductsController : ControllerBase
{
    &#91;HttpPost]
    public IActionResult Create(ProductDto product)
    {
        // This code is NEVER reached if validation fails —
        // the &#91;ApiController] filter already returned 400.
        return Ok(product);
    }
}</code></pre>



<p>If you POST { &#8220;Price&#8221;: 5000 } (missing Name, invalid Price), the response is automatically:</p>



<pre class="wp-block-code"><code>{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Name": &#91;"The Name field is required."],
    "Price": &#91;"The field Price must be between 1 and 1000."]
  },
  "traceId": "00-abc123..."
}</code></pre>



<div class="note">Key points for the interview</div>



<ul class="wp-block-list">
<li>This automatic behavior is <strong>only enabled because of <code>[ApiController]</code></strong> — without it, <code>ModelState.IsValid</code> stays your responsibility to check manually.</li>



<li>The action method <strong>body never executes</strong> when validation fails — it&#8217;s short-circuited earlier in the pipeline.</li>



<li>The response format follows <strong>RFC 7807 (Problem Details for HTTP APIs)</strong>, giving a consistent, machine-readable error structure across your whole API.</li>



<li>You <strong>can customize this behavior</strong> by configuring <code>ApiBehaviorOptions.InvalidModelStateResponseFactory</code> in <code>Program.cs</code>/<code>Startup.cs</code>:</li>
</ul>



<pre class="wp-block-code"><code>  builder.Services.Configure&lt;ApiBehaviorOptions&gt;(options =&gt;
  {
      options.InvalidModelStateResponseFactory = context =&gt;
      {
          // custom response logic
          return new BadRequestObjectResult(context.ModelState);
      };
  });</code></pre>



<p><b>30. What are FromBody, FromRoute, FromQuery, and FromHeader ?</b></p>
<p>These attributes tell ASP.NET Core where in the HTTP request to look for a value when binding it to an action method&#8217;s parameter or model property. This process is called model binding.</p>
<h3>[FromRoute]</h3>
<p>Binds a parameter from the route/URL segment (values captured by the route template).</p>



<pre class="wp-block-code"><code>&#91;HttpGet("{id}")]
public IActionResult GetById(&#91;FromRoute] int id)
{
    // URL: GET /api/products/5  → id = 5
}</code></pre>



<h3>[FromQuery]</h3>
<p>Binds a parameter from the query string.</p>



<pre class="wp-block-code"><code>&#91;HttpGet]
public IActionResult Search(&#91;FromQuery] string name, &#91;FromQuery] int page)
{
    // URL: GET /api/products?name=phone&amp;page=2
}</code></pre>



<h3>[FromBody]</h3>
<p>Binds a parameter from the request body (typically JSON), deserialized into a complex object. Only one parameter per action can use [FromBody].</p>



<pre class="wp-block-code"><code>&#91;HttpPost]
public IActionResult Create(&#91;FromBody] Product product)
{
    // Body: { "name": "Phone", "price": 500 }
}</code></pre>



<h3>[FromHeader]</h3>
<p>Binds a parameter from an HTTP request header.</p>



<pre class="wp-block-code"><code>&#91;HttpGet]
public IActionResult Get(&#91;FromHeader(Name = "X-Api-Key")] string apiKey)
{
    // Reads the "X-Api-Key" header value
}</code></pre>



<p><u>Summary Table:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Attribute</th><th>Source</th><th>Typical Use</th></tr></thead><tbody><tr><td><code>[FromRoute]</code></td><td>URL path segment</td><td>IDs in the route, e.g. <code>/api/products/{id}</code></td></tr><tr><td><code>[FromQuery]</code></td><td>Query string</td><td>Filters, paging, search params, e.g. <code>?page=2&amp;size=10</code></td></tr><tr><td><code>[FromBody]</code></td><td>Request body (JSON)</td><td>Complex objects sent in POST/PUT requests</td></tr><tr><td><code>[FromHeader]</code></td><td>HTTP headers</td><td>API keys, tokens, custom metadata</td></tr><tr><td><code>[FromForm]</code></td><td>Form data</td><td>File uploads, form submissions</td></tr></tbody></table></div>



<p><u>Do you need to specify them explicitly?</u></p>



<p class="wp-block-paragraph">With <code>[ApiController]</code>, ASP.NET Core does <strong>automatic binding source inference</strong>, so in many common cases you don&#8217;t need to write the attribute explicitly:</p>



<ul class="wp-block-list">
<li>Complex types (classes) → inferred as <code>[FromBody]</code></li>



<li>Simple types (<code>int</code>, <code>string</code>, <code>Guid</code>, etc.) that match a route parameter name → inferred as <code>[FromRoute]</code></li>



<li>Simple types that don&#8217;t match a route parameter → inferred as <code>[FromQuery]</code></li>



<li><code>IFormFile</code> / <code>IFormFileCollection</code> → inferred as <code>[FromForm]</code></li>
</ul>



<p class="wp-block-paragraph">You still need to specify <code>[FromHeader]</code> <strong>explicitly</strong> — it&#8217;s never inferred automatically.</p>



<p><b>31. What is the difference between Ok(), Created(), CreatedAtAction(), BadRequest(), NotFound(), and NoContent() ?</b></p>
<p>These are all helper methods available in ControllerBase (and Controller) that return IActionResult types, each mapping to a specific HTTP status code. Using them makes your API responses explicit and RESTful, rather than manually setting status codes everywhere.</p>
<h3>Ok() → 200 OK</h3>
<p>Indicates the request succeeded. Optionally returns data in the response body.</p>



<pre class="wp-block-code"><code>&#91;HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var product = _repo.Find(id);
    return Ok(product); // 200 + product in body
}</code></pre>



<h3>Created() → 201 Created</h3>
<p>Indicates a new resource was successfully created. Requires you to manually provide the URI of the new resource plus the resource itself.</p>



<pre class="wp-block-code"><code>&#91;HttpPost]
public IActionResult Create(Product product)
{
    _repo.Add(product);
    string uri = $"/api/products/{product.Id}";
    return Created(uri, product); // 201 + Location header + body
}</code></pre>



<h3>CreatedAtAction() → 201 Created</h3>
<p>Same as Created(), but instead of manually building the URI string, you point to an action method (and route values), and the framework generates the correct URL for the Location header using routing.</p>



<pre class="wp-block-code"><code>&#91;HttpPost]
public IActionResult Create(Product product)
{
    _repo.Add(product);
    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
    // 201 + Location: /api/products/{id} (auto-generated via route)
}</code></pre>



<div class="noteBlock">Difference between Created() and CreatedAtAction(): Created() needs a hardcoded/manual URI; CreatedAtAction() generates the URI dynamically based on route info of another action — safer against typos and route changes.</div>
<h3>BadRequest() → 400 Bad Request</h3>
<p>Indicates the client sent an invalid request (e.g., failed validation, bad input).</p>



<pre class="wp-block-code"><code>&#91;HttpPost]
public IActionResult Create(Product product)
{
    if (product.Price &lt;= 0)
        return BadRequest("Price must be greater than zero.");

    ...
}</code></pre>



<h3>NotFound() → 404 Not Found</h3>
<p>Indicates the requested resource doesn&#8217;t exist.</p>



<pre class="wp-block-code"><code>&#91;HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var product = _repo.Find(id);
    if (product == null)
        return NotFound(); // 404

    return Ok(product);
}</code></pre>



<h3>NoContent() → 204 No Content</h3>
<p>Indicates the request succeeded, but there&#8217;s no data to return in the response body — common for PUT/DELETE operations.</p>



<pre class="wp-block-code"><code>&#91;HttpDelete("{id}")]
public IActionResult Delete(int id)
{
    var product = _repo.Find(id);
    if (product == null)
        return NotFound();

    _repo.Remove(product);
    return NoContent(); // 204, empty body
}</code></pre>



<p>Summary Table:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Method</th><th>Status Code</th><th>Meaning</th><th>Includes Body?</th><th>Includes Location Header?</th></tr></thead><tbody><tr><td><code>Ok()</code></td><td>200</td><td>Success</td><td>✅ Yes (optional)</td><td>❌ No</td></tr><tr><td><code>Created()</code></td><td>201</td><td>Resource created (manual URI)</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td><code>CreatedAtAction()</code></td><td>201</td><td>Resource created (URI via route)</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td><code>BadRequest()</code></td><td>400</td><td>Invalid client request</td><td>✅ Optional (error details)</td><td>❌ No</td></tr><tr><td><code>NotFound()</code></td><td>404</td><td>Resource doesn&#8217;t exist</td><td>✅ Optional</td><td>❌ No</td></tr><tr><td><code>NoContent()</code></td><td>204</td><td>Success, nothing to return</td><td>❌ No</td><td>❌ No</td></tr></tbody></table></div>



<p><b>32. What is content negotiation ?</b></p>
<p>Content negotiation is the mechanism by which a Web API decides what format to send the response back in (e.g., JSON, XML, plain text), based on what the client says it can accept, rather than the server always returning a fixed format.</p>
<p>It works using the standard HTTP Accept request header, and the corresponding Content-Type response header.</p>



<p><u>How it works:</u></p>



<ol class="wp-block-list">
<li>The client sends a request with an <code>Accept</code> header specifying the format(s) it wants:</li>
</ol>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
   GET /api/products/1
   Accept: application/json
</pre></div>


<ol start="2" class="wp-block-list">
<li>ASP.NET Core&#8217;s content negotiation system looks at the registered <strong>output formatters</strong> and picks the one matching the requested media type.</li>



<li>The response is serialized accordingly, and the <code>Content-Type</code> header reflects the chosen format:</li>
</ol>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
   Content-Type: application/json
</pre></div>


<p class="wp-block-paragraph">If the client instead sends:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
Accept: application/xml
</pre></div>


<p>&#8230;and XML formatters are configured, the same endpoint returns XML instead — <strong>without changing any controller code</strong>.</p>
<p><u>Example:</u></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpGet(&quot;{id}&quot;)]
public IActionResult GetById(int id)
{
    var product = _repo.Find(id);
    return Ok(product); // format decided by content negotiation
}
</pre></div>


<ul class="wp-block-list">
<li>Request with <code>Accept: application/json</code> → response body: <code>{"id":1,"name":"Phone"}</code></li>



<li>Request with <code>Accept: application/xml</code> → response body: <code>&lt;Product&gt;&lt;Id&gt;1&lt;/Id&gt;&lt;Name&gt;Phone&lt;/Name&gt;&lt;/Product&gt;</code> (if XML formatter is added)</li>
</ul>



<p><u>Default behavior in ASP.NET Core:</u></p>



<ul class="wp-block-list">
<li><strong>JSON is the default</strong> output formatter (<code>System.Text.Json</code> since ASP.NET Core 3.0, previously <code>Newtonsoft.Json</code>).</li>



<li><strong>XML is NOT included by default</strong> — you must explicitly add it:</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
  builder.Services.AddControllers()
      .AddXmlSerializerFormatters();
</pre></div>


<ul class="wp-block-list">
<li>If the <code>Accept</code> header requests a format the server <strong>doesn&#8217;t support</strong>, ASP.NET Core by default <strong>ignores the header and returns the default formatter&#8217;s output (JSON)</strong> rather than a <code>406 Not Acceptable</code> — unless you explicitly enable strict negotiation:</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; title: ; notranslate">
  builder.Services.AddControllers(options =&gt;
  {
      options.ReturnHttpNotAcceptable = true; // returns 406 if format unsupported
  });
</pre></div>


<p><u>Input vs Output formatters:</u></p>



<p class="wp-block-paragraph">Content negotiation usually refers to <strong>output</strong> (response), but there&#8217;s a related concept for <strong>input</strong>:</p>



<ul class="wp-block-list">
<li><strong>Output formatters</strong> — serialize the response based on <code>Accept</code> header.</li>



<li><strong>Input formatters</strong> — deserialize the incoming request body based on <code>Content-Type</code> header (e.g., telling the server the request body is JSON).</li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"></p>
</blockquote>



<p><b>33. Explain  IActionResult and ActionResult&lt;T></b></p>



<p>Both are return types used in Web API action methods to represent an HTTP response, but they differ in flexibility, type-safety, and how well they play with Swagger/OpenAPI documentation.</p>
<h3>IActionResult</h3>
<p>An interface representing any action result. It&#8217;s the common return type shared by all the helper methods (Ok(), NotFound(), BadRequest(), NoContent(), etc.), since they all return classes implementing IActionResult (OkObjectResult, NotFoundResult, etc.).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpGet(&quot;{id}&quot;)]
public IActionResult GetById(int id)
{
    var product = _repo.Find(id);
    if (product == null)
        return NotFound();       // NotFoundResult

    return Ok(product);          // OkObjectResult
}
</pre></div>


<p><u>Characteristics:</u></p>
<ul>
<li>Flexible — lets you return any combination of result types (Ok, NotFound, BadRequest, etc.) from the same method.</li>
<li>Not strongly typed — the compiler doesn&#8217;t know what the actual success payload type is; tools like Swagger can&#8217;t automatically infer the response model unless you add [ProducesResponseType(typeof(Product), 200)] manually.</li>
</ul>
<h3>ActionResult&lt;T></h3>
<p>A generic class introduced in ASP.NET Core 2.1 that combines the flexibility of IActionResult with strong typing of the actual data being returned. It supports implicit conversion from both your data type T and standard action results.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpGet(&quot;{id}&quot;)]
public ActionResult&lt;Product&gt; GetById(int id)
{
    var product = _repo.Find(id);
    if (product == null)
        return NotFound();       // implicit conversion to ActionResult&lt;Product&gt;

    return product;              // implicit conversion, wraps in Ok(product) automatically
    // return Ok(product);       // also valid
}
</pre></div>


<p class="wp-block-paragraph"><u>Characteristics:</u></p>



<ul class="wp-block-list">
<li>Strongly typed — the method signature tells you (and tooling) exactly what successful responses look like: <code>Product</code>.</li>



<li>Better <strong>Swagger/OpenAPI</strong> integration — since the return type is known, tools can automatically generate accurate response schemas without needing extra <code>[ProducesResponseType]</code> attributes.</li>



<li>Still supports returning other status results (<code>NotFound()</code>, <code>BadRequest()</code>, etc.) via implicit conversion — you don&#8217;t lose flexibility.</li>



<li>You can return <code>T</code> directly (e.g., <code>return product;</code>) and it automatically becomes a <code>200 OK</code> with that body.</li>
</ul>



<p><u>Summary Table:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Aspect</th><th><code>IActionResult</code></th><th><code>ActionResult&lt;T&gt;</code></th></tr></thead><tbody><tr><td>Type</td><td>Interface</td><td>Generic class</td></tr><tr><td>Strong typing of success payload</td><td>❌ No</td><td>✅ Yes</td></tr><tr><td>Can return multiple result types (<code>Ok</code>, <code>NotFound</code>, etc.)</td><td>✅ Yes</td><td>✅ Yes</td></tr><tr><td>Can return raw data directly (<code>return product;</code>)</td><td>❌ No (must wrap in <code>Ok(product)</code>)</td><td>✅ Yes (implicit conversion)</td></tr><tr><td>Swagger/OpenAPI schema accuracy</td><td>⚠️ Needs <code>[ProducesResponseType]</code> for accuracy</td><td>✅ Better out-of-the-box</td></tr><tr><td>Introduced in</td><td>Original MVC/Web API</td><td>ASP.NET Core 2.1+</td></tr></tbody></table></div>



<p><b>34. What HTTP status code should be returned after successfully creating a resource ?</b></p>
<p><code>201 Created</code> is the correct status code after successfully creating a new resource via a POST request.</p>
<p><u>Why 201, not 200?</u></p>
<p>In ASP.NET Core, use CreatedAtAction() (preferred) or Created():</p>



<ul>
<li>200 OK means &#8220;the request succeeded&#8221; — generic, doesn&#8217;t say anything about a new resource being created.</li>
<li>201 Created specifically means &#8220;a new resource was created as a result of this request,&#8221; and per HTTP spec, the response should ideally include:
<ul>
<li>A Location header pointing to the URI of the newly created resource.</li>
<li>Optionally, the created resource itself in the response body.</li>
</ul>
</li>
</ul>



<pre class="wp-block-code"><code>&#91;HttpPost]
public IActionResult Create(Product product)
{
    _repo.Add(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
    // Status: 201 Created
    // Location: /api/products/{id}
    // Body: the created product
}</code></pre>



<p>Response headers/body look like:</p>



<pre class="wp-block-code"><code>HTTP/1.1 201 Created
Location: /api/products/5
Content-Type: application/json

{ "id": 5, "name": "Phone", "price": 500 }</code></pre>



<p><u>Common mistake in interviews:</u></p>
<p>Returning Ok(product) (200) after a POST — it works, but it&#8217;s not RESTfully correct. Interviewers often ask this specifically to check if you know the difference between &#8220;successful operation&#8221; (200) vs &#8220;resource created&#8221; (201) semantics.</p>



<div classs="note">Quick reference for common scenarios</div>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Operation</th><th>HTTP Verb</th><th>Correct Status Code</th></tr></thead><tbody><tr><td>Fetch existing resource</td><td>GET</td><td>200 OK</td></tr><tr><td>Create new resource</td><td>POST</td><td><strong>201 Created</strong></td></tr><tr><td>Update existing resource</td><td>PUT</td><td>200 OK or 204 No Content</td></tr><tr><td>Partial update</td><td>PATCH</td><td>200 OK or 204 No Content</td></tr><tr><td>Delete resource</td><td>DELETE</td><td>204 No Content</td></tr><tr><td>Resource not found</td><td>any</td><td>404 Not Found</td></tr><tr><td>Invalid input</td><td>any</td><td>400 Bad Request</td></tr></tbody></table></div>



<p><b>35. What is the difference between PUT and PATCH ? How do you implement PATCH in ASP.NET Core ?</b></p>



<p class="wp-block-paragraph">Both are HTTP verbs used to <strong>update</strong> an existing resource, but they differ in <strong>scope</strong> of the update.</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Aspect</th><th>PUT</th><th>PATCH</th></tr></thead><tbody><tr><td><strong>Purpose</strong></td><td>Replace the <strong>entire</strong> resource</td><td>Apply a <strong>partial</strong> update to the resource</td></tr><tr><td><strong>Request body</strong></td><td>Must contain the <strong>complete</strong> representation of the resource</td><td>Contains only the <strong>fields that need to change</strong></td></tr><tr><td><strong>Idempotent?</strong></td><td>✅ Yes — sending the same request multiple times produces the same result</td><td>⚠️ Typically yes in practice, but not guaranteed by spec depending on how the patch is defined</td></tr><tr><td><strong>Missing fields in body</strong></td><td>Missing fields are typically <strong>overwritten with null/default</strong> (since it&#8217;s a full replace)</td><td>Missing fields are <strong>left untouched</strong></td></tr><tr><td><strong>Typical use case</strong></td><td>&#8220;Replace this entire product with this new version&#8221;</td><td>&#8220;Just update the price of this product&#8221;</td></tr></tbody></table></div>



<p>Example scenario &#8211; Given a resource:</p>



<pre class="wp-block-code"><code>{ "id": 1, "name": "Phone", "price": 500, "stock": 20 }</code></pre>



<p>PUT request body (must send the whole object):</p>



<pre class="wp-block-code"><code>{ "id": 1, "name": "Phone", "price": 600, "stock": 20 }</code></pre>



<p>If you forgot to include stock, it might get reset to 0/null — because PUT expects the complete resource.</p>
<p>PATCH request body (only what changes):</p>



<pre class="wp-block-code"><code>{ "price": 600 }</code></pre>



<p>Only price is updated; name and stock remain untouched..</p>
<h3>Implementing PUT in ASP.NET Core (for comparison)</h3>



<pre class="wp-block-code"><code>&#91;HttpPut("{id}")]
public IActionResult Update(int id, Product updatedProduct)
{
    var product = _repo.Find(id);
    if (product == null) return NotFound();

    product.Name = updatedProduct.Name;
    product.Price = updatedProduct.Price;
    product.Stock = updatedProduct.Stock;

    return NoContent(); // 204
}</code></pre>



<h3 class="wp-block-heading">Implementing PATCH in ASP.NET Core</h3>



<p class="wp-block-paragraph">There are two common approaches:</p>



<p class="wp-block-paragraph"><u>Approach 1: JSON Patch (RFC 6902) — the &#8220;standard&#8221; way:</u></p>



<p class="wp-block-paragraph">Uses <code>Microsoft.AspNetCore.JsonPatch</code> with a <code>JsonPatchDocument&lt;T&gt;</code>. The client sends a series of <strong>operations</strong> (add, replace, remove) rather than raw field values.</p>



<p class="wp-block-paragraph"><strong>Setup:</strong></p>



<pre class="wp-block-code"><code>dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson

builder.Services.AddControllers().AddNewtonsoftJson();</code></pre>



<p class="wp-block-paragraph"><strong>Controller:</strong></p>



<pre class="wp-block-code"><code>&#91;HttpPatch("{id}")]
public IActionResult Patch(int id, &#91;FromBody] JsonPatchDocument&lt;Product&gt; patchDoc)
{
    if (patchDoc == null) return BadRequest();

    var product = _repo.Find(id);
    if (product == null) return NotFound();

    patchDoc.ApplyTo(product, ModelState);

    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    return NoContent();
}</code></pre>



<p><b>Client request body (JSON Patch format):</b></p>



<pre class="wp-block-code"><code>&#91;
  { "op": "replace", "path": "/price", "value": 600 }
]</code></pre>



<ul class="wp-block-list">
<li><code>op</code> — the operation: <code>replace</code>, <code>add</code>, <code>remove</code>, <code>copy</code>, <code>move</code>, <code>test</code></li>



<li><code>path</code> — which property to modify</li>



<li><code>value</code> — the new value</li>
</ul>



<p class="wp-block-paragraph"><u>Approach 2: Simple DTO-based partial update (more common in practice)</u></p>



<p class="wp-block-paragraph">Instead of the formal JSON Patch spec, many real-world APIs just accept a partial DTO where only provided fields are updated (often using nullable properties to distinguish &#8220;not provided&#8221; from &#8220;set to default&#8221;).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class ProductPatchDto
{
    public string? Name { get; set; }
    public decimal? Price { get; set; }
    public int? Stock { get; set; }
}

&#x5B;HttpPatch(&quot;{id}&quot;)]
public IActionResult Patch(int id, ProductPatchDto dto)
{
    var product = _repo.Find(id);
    if (product == null) return NotFound();

    if (dto.Name != null) product.Name = dto.Name;
    if (dto.Price.HasValue) product.Price = dto.Price.Value;
    if (dto.Stock.HasValue) product.Stock = dto.Stock.Value;

    return NoContent();
}
</pre></div>


<p>This approach is simpler to consume from typical frontend clients (just send changed fields as plain JSON) but doesn&#8217;t follow the formal JSON Patch RFC.</p>
<p><b>36. What is JSON Patch ?</b></p>



<p class="wp-block-paragraph"><strong>JSON Patch</strong> is a standardized format (defined in <strong>RFC 6902</strong>) for describing <strong>partial modifications</strong> to a JSON document. Instead of sending the entire updated resource, the client sends a list of <strong>operations</strong> that describe exactly <em>what</em> should change — making it ideal for implementing HTTP <code>PATCH</code> requests.</p>



<p><u>Structure:</u></p>



<p class="wp-block-paragraph">A JSON Patch document is a <strong>JSON array</strong> of operation objects. Each operation has:</p>



<ul class="wp-block-list">
<li><code>op</code> — the operation type: <code>add</code>, <code>remove</code>, <code>replace</code>, <code>move</code>, <code>copy</code>, or <code>test</code></li>



<li><code>path</code> — a <strong>JSON Pointer</strong> (RFC 6901) indicating which part of the document to target</li>



<li><code>value</code> — the new value (required for <code>add</code>, <code>replace</code>, <code>test</code>; not used for <code>remove</code>)</li>
</ul>



<pre class="wp-block-code"><code>&#91;
  { "op": "replace", "path": "/price", "value": 600 },
  { "op": "add", "path": "/tags/-", "value": "new-arrival" },
  { "op": "remove", "path": "/discontinued" }
]</code></pre>



<p><u>The six operations:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Operation</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code>add</code></td><td>Adds a value at the given path (or appends to an array with <code>-</code>)</td><td><code>{ "op": "add", "path": "/stock", "value": 50 }</code></td></tr><tr><td><code>remove</code></td><td>Removes the value at the given path</td><td><code>{ "op": "remove", "path": "/discountCode" }</code></td></tr><tr><td><code>replace</code></td><td>Replaces the existing value at the given path</td><td><code>{ "op": "replace", "path": "/price", "value": 600 }</code></td></tr><tr><td><code>move</code></td><td>Moves a value from one path to another</td><td><code>{ "op": "move", "from": "/oldField", "path": "/newField" }</code></td></tr><tr><td><code>copy</code></td><td>Copies a value from one path to another</td><td><code>{ "op": "copy", "from": "/name", "path": "/displayName" }</code></td></tr><tr><td><code>test</code></td><td>Checks that a value at a path equals the given value (used for conditional/optimistic operations)</td><td><code>{ "op": "test", "path": "/price", "value": 500 }</code></td></tr></tbody></table></div>



<p><u>Example in context &#8211; Given this resource:</u></p>



<pre class="wp-block-code"><code>{ "id": 1, "name": "Phone", "price": 500, "stock": 20 }</code></pre>



<p class="wp-block-paragraph">This JSON Patch request:</p>



<pre class="wp-block-code"><code>&#91;
  { "op": "replace", "path": "/price", "value": 600 },
  { "op": "replace", "path": "/stock", "value": 25 }
]</code></pre>



<p class="wp-block-paragraph">…results in:</p>



<pre class="wp-block-code"><code>{ "id": 1, "name": "Phone", "price": 600, "stock": 25 }</code></pre>



<p>Only the specified fields change — everything else stays as-is.</p>
<h3>JSON Patch in ASP.NET Core</h3>
<p>ASP.NET Core supports JSON Patch through the Microsoft.AspNetCore.JsonPatch package and the <code>JsonPatchDocument&lt;T></code> type:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpPatch(&quot;{id}&quot;)]
public IActionResult Patch(int id, &#x5B;FromBody] JsonPatchDocument&lt;Product&gt; patchDoc)
{
    var product = _repo.Find(id);
    if (product == null) return NotFound();

    patchDoc.ApplyTo(product, ModelState);

    if (!ModelState.IsValid)
        return BadRequest(ModelState);

    return NoContent();
}
</pre></div>


<p class="wp-block-paragraph"><code>ApplyTo()</code> executes each operation from the patch document against the target object.</p>



<h3>JSON Patch vs. Simple Partial DTO</h3>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Aspect</th><th>JSON Patch (RFC 6902)</th><th>Simple Partial DTO</th></tr></thead><tbody><tr><td>Format</td><td>Array of operations (<code>op</code>, <code>path</code>, <code>value</code>)</td><td>Plain JSON object with only changed fields</td></tr><tr><td>Standardized?</td><td>✅ Yes (RFC 6902)</td><td>❌ No, custom convention</td></tr><tr><td>Complexity</td><td>More powerful (array manipulation, move, copy, test)</td><td>Simpler, easier for frontend devs to construct</td></tr><tr><td>Common in practice</td><td>Less common outside strict REST APIs</td><td>Very common in real-world apps</td></tr></tbody></table></div>



<p><b>37. How do you return XML from an ASP.NET Core Web API and how do you consume XML in an ASP.NET Core API ? How do you implement global API exception handling ?</b></p>
<p>By default, ASP.NET Core Web API only supports JSON. To support XML, you register XML formatters via AddXmlSerializerFormatters() (or AddXmlDataContractSerializerFormatters() for more complex types) in Program.cs. Once registered, it works both ways: for output, content negotiation returns XML when the client sends Accept: application/xml; for input, the framework deserializes the request body into your model when Content-Type: application/xml is set — all without changing any controller code, since the formatter selection happens transparently based on HTTP headers.</p>
<h3>Global API Exception Handling in ASP.NET Core</h3>
<p>Instead of try/catch in every action, ASP.NET Core supports centralized exception handling. The classic approach is app.UseExceptionHandler() middleware, which catches unhandled exceptions from the whole pipeline and returns a ProblemDetails response. Since .NET 8, the recommended approach is implementing IExceptionHandler — a DI-friendly abstraction where you can register multiple handlers that run in order. For custom exception-to-status-code mapping — like a NotFoundException becoming 404 — I&#8217;d write custom middleware or an IExceptionHandler that inspects the exception type. Exception filters (IExceptionFilter) are an older, MVC-only alternative that won&#8217;t catch exceptions outside the MVC pipeline, so middleware-based approaches are generally preferred.</p>
<h2>Minimal APIs Interview Questions</h2>
<p><a href="https://www.yogihosting.com/aspnet-core-minimal-api/">ASP.NET CORE Minimal APIs</a> matter in interviews because they&#8217;re now the default template in modern .NET, they&#8217;re heavily used in microservices and cloud-native architectures, and they test whether a candidate understands the underlying ASP.NET Core pipeline rather than relying purely on MVC conventions. Being asked about them is really a proxy for: &#8216;Is this candidate&#8217;s knowledge current, and do they understand trade-offs between lightweight and full MVC-based APIs?</p>



<p class="wp-block-paragraph">Here&#8217;s why interviewers increasingly focus on Minimal APIs and why you need to be prepared:</p>



<p>a. <b>Minimal APIs are now the default template in .NET</b></p>



<ul class="wp-block-list">
<li>Since .NET 6+, <code>dotnet new webapi</code> generates a <strong>Minimal API</strong> project by default (not a controller-based one).</li>



<li>Any interviewer working with modern .NET codebases (.NET 6/7/8/9) will likely be using or evaluating Minimal APIs in real projects — so it&#8217;s now considered <strong>baseline knowledge</strong>, not a niche topic.</li>
</ul>



<p>b. <strong>It tests whether your knowledge is current or outdated</strong></p>



<ul class="wp-block-list">
<li>Many candidates learned ASP.NET Core only through <strong>Controller-based Web API</strong> (older tutorials, older jobs).</li>



<li>Asking about Minimal APIs immediately reveals whether you&#8217;ve kept up with the framework&#8217;s evolution — a strong signal of whether you follow the ecosystem or are stuck on legacy patterns.</li>
</ul>



<p>c. <strong>Companies are actively migrating toward it for microservices</strong></p>



<ul class="wp-block-list">
<li>Minimal APIs are lightweight, have <strong>less boilerplate</strong>, and start faster — making them attractive for <strong>microservices</strong>, serverless functions, and containerized workloads (fewer resources, smaller footprint).</li>



<li>If a company is building microservices or cloud-native systems, they specifically want engineers comfortable with this style.</li>
</ul>



<p>d. <strong>It tests understanding of the underlying pipeline, not just syntax</strong></p>



<ul class="wp-block-list">
<li>Minimal APIs strip away the MVC abstraction (controllers, filters, model binding conventions), forcing you to understand things more directly:
<ul class="wp-block-list">
<li><code>WebApplication</code> / <code>WebApplicationBuilder</code></li>



<li>Endpoint routing (<code>app.MapGet</code>, <code>app.MapPost</code>, etc.)</li>



<li>Middleware ordering</li>



<li>Dependency injection at the endpoint level</li>
</ul>
</li>



<li>Interviewers use it to test whether you <strong>understand the framework</strong>, rather than just memorized Controller patterns.</li>
</ul>



<p>e. <strong>It reveals knowledge of trade-offs — a sign of seniority</strong></p>



<ul class="wp-block-list">
<li>Knowing <strong>when to use Minimal APIs vs Controllers</strong> (e.g., simple CRUD/microservices vs large enterprise apps needing filters, model binding conventions, versioning, <code>[ApiController]</code> conventions) shows <strong>architectural judgment</strong>, not just syntax memorization — which is exactly what separates mid-level from senior candidates.</li>
</ul>



<p>f. <strong>Performance-conscious teams specifically look for it</strong></p>



<ul class="wp-block-list">
<li>Minimal APIs have <strong>less overhead</strong> than MVC controllers (no controller activation, fewer filters by default), which matters for teams optimizing for <strong>high-throughput APIs</strong>.</li>



<li>Being able to discuss this shows you understand performance implications of architectural choices — a common senior-level interview theme.</li>
</ul>



<p>g. <strong>It often comes packaged with other modern-era topics</strong></p>



<ul class="wp-block-list">
<li>Once you know Minimal APIs, interviewers will likely chain into related <strong>modern .NET topics</strong> that are also now common:
<ul class="wp-block-list">
<li><code>IExceptionHandler</code> (rather than filters)</li>



<li><code>Program.cs</code> top-level statements (no <code>Startup.cs</code>)</li>



<li>Native AOT compilation support</li>



<li>Endpoint filters (<code>AddEndpointFilter</code>) instead of MVC filters</li>
</ul>
</li>



<li>These are all part of the same &#8220;modern .NET&#8221; cluster interviewers use to gauge how current your skills are.</li>
</ul>



<p>h. <strong>It&#8217;s a quick way to differentiate candidates in a crowded market</strong></p>



<ul class="wp-block-list">
<li>Since ASP.NET Core roles get a huge number of applicants, interviewers use <strong>Minimal API knowledge as a filter</strong> — it&#8217;s a low-effort way to separate candidates who only know &#8220;textbook MVC&#8221; from those with genuine hands-on modern experience.</li>
</ul>



<p><b>38. What are Minimal APIs and how are Minimal APIs different from controller-based APIs ? When would you choose Minimal APIs over controllers?</b></p>
<p>Minimal APIs (introduced in .NET 6) let you build HTTP APIs with minimal ceremony — no controllers, no boilerplate, no attribute routing classes. You define routes and handlers directly against the <code>WebApplication</code> instance.</p>



<pre class="wp-block-code"><code>var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/products/{id}", (int id, IProductService svc) =&gt; svc.GetById(id));
app.MapPost("/products", (Product p, IProductService svc) =&gt; svc.Add(p));

app.Run();</code></pre>



<p class="wp-block-paragraph">That&#8217;s a complete, working API — no <code>Program.cs</code> split, no <code>[ApiController]</code>, no separate controller file.</p>



<h3>Key Differences from Controller-Based APIs</h3>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Aspect</th><th>Minimal APIs</th><th>Controller-Based APIs</th></tr></thead><tbody><tr><td><strong>Structure</strong></td><td>Endpoints defined as lambdas/methods mapped directly to routes</td><td>Classes inheriting <code>ControllerBase</code>, actions as methods</td></tr><tr><td><strong>Routing</strong></td><td><code>app.MapGet/MapPost/...</code> fluent calls</td><td>Attribute routing (<code>[Route]</code>, <code>[HttpGet]</code>) or conventional routing</td></tr><tr><td><strong>Boilerplate</strong></td><td>Very little — no class, no attributes required</td><td>More ceremony — class, base type, attributes</td></tr><tr><td><strong>Filters</strong></td><td>Endpoint filters (<code>IEndpointFilter</code>), lighter-weight</td><td>Action filters, full filter pipeline (Authorization, Resource, Action, Exception, Result)</td></tr><tr><td><strong>Model binding</strong></td><td>Explicit and simpler ( <code>[FromBody]</code>, <code>[FromRoute]</code>, etc., but less &#8220;magic&#8221;)</td><td>Rich automatic binding via <code>[ApiController]</code> inference</td></tr><tr><td><strong>Validation</strong></td><td>No built-in automatic model validation (must do manually or via libraries like FluentValidation, or newer .NET 7+ improvements)</td><td><code>[ApiController]</code> gives automatic <code>400</code> on invalid <code>ModelState</code></td></tr><tr><td><strong>Documentation/Swagger</strong></td><td>Supported but you often annotate more explicitly (<code>WithName</code>, <code>Produces</code>, etc.)</td><td>Well-integrated via attributes and conventions</td></tr><tr><td><strong>Performance</strong></td><td>Slightly leaner/faster — fewer abstractions, less reflection overhead</td><td>Marginally more overhead due to MVC pipeline richness</td></tr><tr><td><strong>Testability</strong></td><td>Handlers are just delegates — easy to unit test in isolation</td><td>Controllers are classes — also easily testable, more familiar to MVC devs</td></tr><tr><td><strong>Organization for large APIs</strong></td><td>Can get messy if not organized (people use extension methods/route groups to manage)</td><td>Naturally organizes by resource via separate controller classes</td></tr><tr><td><strong>Convention over configuration</strong></td><td>Minimal — you&#8217;re explicit about most things</td><td>MVC provides many conventions out of the box</td></tr></tbody></table></div>



<p>Notably, both ultimately run on the same underlying <strong>routing and hosting infrastructure</strong> (<code>EndpointRouteBuilder</code>) — Minimal APIs aren&#8217;t a separate framework, just a lighter-weight way to register endpoints.</p>
<h3>When to Choose Minimal APIs vs Controllers</h3>
<p><u>Choose Minimal APIs when:</u></p>
<ul>
<li>Building microservices or small, focused APIs with few endpoints.</li>
<li>Building serverless functions or lightweight backends (e.g., Azure Functions-style workloads).</li>
<li>where startup time and memory footprint matter.</li>
<li>You want fast prototyping or simple CRUD endpoints without ceremony.</li>
<li>The team is comfortable with a more functional/lambda-based style.</li>
<li>You don&#8217;t need the full MVC filter pipeline or complex model binding conventions.</li>
</ul>
<p><u>Choose Controllers when:</u></p>
<ul>
<li>Building large, complex APIs with many endpoints, where organizing by controller/resource keeps things maintainable.</li>
<li>You need the full MVC feature set: model binding conventions, automatic validation via [ApiController], rich filter pipeline, versioning conventions, etc.</li>
<li>The team is already experienced with MVC patterns and conventions.</li>
<li>You need better built-in support for things like API versioning, conventions-based Swagger/OpenAPI generation, or complex content negotiation.</li>
</ul>
<div class="starBlock">
<u>Interview soundbite:</u> Minimal APIs reduce ceremony and are ideal for small services, microservices, and lightweight endpoints, while controller-based APIs offer richer conventions, filters, and structure for larger, more complex applications. Both use the same underlying ASP.NET Core routing and hosting model — the choice is about developer experience and project scale, not fundamentally different runtime capability.
</div>
<p><b>39. Explain what is MapGet(), MapPost() and MapGroup()</b></p>
<p><span class="code">MapGet()</span> and <span class="code">MapPost()</span> are extension methods on IEndpointRouteBuilder (implemented by WebApplication) used to register Minimal API endpoints for specific HTTP verbs.</p>



<pre class="wp-block-code"><code>app.MapGet("/products", () =&gt; "Get all products");
app.MapPost("/products", (Product p) =&gt; "Product created");</code></pre>



<ul>
<li>MapGet(pattern, handler) — registers an endpoint that responds to HTTP GET requests at the given route pattern.</li>
<li>MapPost(pattern, handler) — registers an endpoint that responds to HTTP POST requests.</li>
</ul>



<p class="wp-block-paragraph">There are corresponding siblings too: <code>MapPut()</code>, <code>MapDelete()</code>, <code>MapPatch()</code>, and the generic <code>MapMethods()</code> (for custom/multiple verbs).</p>



<pre class="wp-block-code"><code>app.MapGet("/products/{id:int}", (int id, IProductService svc) =&gt;
{
    var product = svc.GetById(id);
    return product is not null ? Results.Ok(product) : Results.NotFound();
})
.WithName("GetProductById")
.Produces&lt;Product&gt;(200)
.Produces(404);</code></pre>



<ul>
<li>The route pattern can include route constraints ({id:int}), optional segments, etc.</li>
<li>The handler delegate supports full dependency injection (parameters are resolved from DI, route values, query string, or body automatically based on type/binding rules).</li>
<li>Both return an IEndpointConventionBuilder/RouteHandlerBuilder, letting you chain metadata: .WithName(), .WithTags(), .Produces<T>(), .RequireAuthorization(), .AddEndpointFilter(), etc.</li>
</ul>



<h3>MapGroup()</h3>
<p>Introduced in .NET 7, <span class="code">MapGroup()</span> lets you group related endpoints under a common route prefix and apply shared configuration (filters, auth, metadata) to all of them at once — solving the &#8220;Minimal APIs get messy at scale&#8221; problem.</p>



<pre class="wp-block-code"><code>var products = app.MapGroup("/products")
                   .WithTags("Products")
                   .RequireAuthorization();

products.MapGet("/", () =&gt; "Get all products");
products.MapGet("/{id}", (int id) =&gt; $"Get product {id}");
products.MapPost("/", (Product p) =&gt; "Created");</code></pre>



<p class="wp-block-paragraph">This is equivalent to registering <code>/products</code>, <code>/products/{id}</code>, etc., but:</p>



<ul class="wp-block-list">
<li>The <strong>prefix</strong> (<code>/products</code>) is applied automatically to all routes in the group.</li>



<li>Any <code>.WithTags()</code>, <code>.RequireAuthorization()</code>, <code>.AddEndpointFilter()</code>, <code>.MapToApiVersion()</code> etc. chained on the group <strong>cascades to every endpoint inside it</strong> — no need to repeat it per route.</li>



<li>Groups can be <strong>nested</strong> (a group within a group) for further sub-organization.</li>



<li>You can also extract group definitions into extension methods per feature/resource, giving you something like a &#8220;controller-lite&#8221; structure:</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public static class ProductEndpoints
{
    public static void MapProductEndpoints(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup(&quot;/products&quot;).WithTags(&quot;Products&quot;);
        group.MapGet(&quot;/&quot;, GetAll);
        group.MapGet(&quot;/{id}&quot;, GetById);
        group.MapPost(&quot;/&quot;, Create);
    }
}

// Program.cs
app.MapProductEndpoints();
</pre></div>


<p><b>40. How do you apply authorization to Minimal API endpoints ?</b></p>
<p>In Minimal APIs, you apply authorization using the <span class="code">RequireAuthorization()</span> extension method on the endpoint, instead of using <span class="code">[Authorize]</span> attributes (which is how you do it in MVC controllers).</p>



<pre class="wp-block-code"><code>var app = builder.Build();

app.MapGet("/secure-data", () =&gt; "This is protected")
   .RequireAuthorization();</code></pre>



<p>This requires the user to simply be authenticated (no specific policy).</p>
<p>Applying a specific policy:</p>



<pre class="wp-block-code"><code>builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy("AdminOnly", policy =&gt;
        policy.RequireRole("Admin"));
});

app.MapGet("/admin", () =&gt; "Admin area")
   .RequireAuthorization("AdminOnly");</code></pre>



<p>Requiring specific roles or claims inline:</p>



<pre class="wp-block-code"><code>app.MapGet("/manager", () =&gt; "Manager data")
   .RequireAuthorization(policy =&gt; policy.RequireRole("Manager"));</code></pre>



<p>Applying authorization to a group of endpoints:</p>
<p>Instead of chaining <code>RequireAuthorization()</code> on every route, you can group endpoints and apply it once:</p>



<pre class="wp-block-code"><code>var group = app.MapGroup("/api/orders")
                .RequireAuthorization();

group.MapGet("/", GetOrders);
group.MapPost("/", CreateOrder);</code></pre>



<p>All endpoints inside that group inherit the authorization requirement.</p>
<p>Allowing anonymous access to specific endpoints in a protected group.</p>
<p>If a group is secured but one endpoint should be public:</p>



<pre class="wp-block-code"><code>group.MapGet("/public-info", GetPublicInfo)
     .AllowAnonymous();</code></pre>



<p>Prerequisites:</p>
<p>For <code>RequireAuthorization()</code> to work, you still need the standard middleware pipeline set up:</p>



<pre class="wp-block-code"><code>app.UseAuthentication();
app.UseAuthorization();</code></pre>



<p>And the authorization services registered:</p>



<pre class="wp-block-code"><code>builder.Services.AddAuthorization();</code></pre>



<p>Key interview point to mention &#8211; The main conceptual shift from MVC is: authorization is applied fluently via extension methods on the endpoint (or route group) rather than declaratively via attributes on a controller/action. This fits the Minimal API philosophy of composing behavior through method chaining rather than decorating classes.</p>
<p><b>41. How do you inject a service into a Minimal API endpoint ? How do you perform validation in Minimal APIs?</b></p>
<p>Minimal APIs support parameter-based dependency injection — you simply add the service as a parameter to the route handler delegate, and the framework resolves it from the DI container automatically.</p>
<p><u>Basic Example:</u></p>



<pre class="wp-block-code"><code>builder.Services.AddScoped&lt;IOrderService, OrderService&gt;();

var app = builder.Build();

app.MapGet("/orders", (IOrderService orderService) =&gt;
{
    return orderService.GetAllOrders();
});</code></pre>



<p>No attributes are needed — the runtime inspects the parameter types and resolves them from the service container, similar to constructor injection in MVC controllers.</p>
<p><u>Explicit binding with [FromServices]:</u></p>
<p>Usually unnecessary since Minimal APIs infer services automatically, but you can be explicit (useful in ambiguous cases or with certain complex types):</p>



<pre class="wp-block-code"><code>app.MapGet("/orders", (&#91;FromServices] IOrderService orderService) =&gt;
{
    return orderService.GetAllOrders();
});</code></pre>



<p><u>Mixing services with route/query parameters and request body:</u></p>



<pre class="wp-block-code"><code>app.MapPost("/orders/{customerId}", (
    int customerId,
    &#91;FromBody] OrderDto order,
    IOrderService orderService,
    ILogger&lt;Program&gt; logger) =&gt;
{
    logger.LogInformation("Creating order for {CustomerId}", customerId);
    return orderService.CreateOrder(customerId, order);
});</code></pre>



<p>The framework figures out parameter sources using conventions: route values, query strings, body, services, etc.</p>
<h3>Validation in Minimal APIs</h3>
<p>Unlike MVC controllers, Minimal APIs do not have built-in automatic model validation (no automatic ModelState.IsValid behavior tied to [ApiController]). You have to handle validation explicitly.</p>



<pre class="wp-block-code"><code>app.MapPost("/orders", (OrderDto order) =&gt;
{
    if (string.IsNullOrWhiteSpace(order.ProductName) || order.Quantity &lt;= 0)
    {
        return Results.ValidationProblem(new Dictionary&lt;string, string&#91;]&gt;
        {
            { "Quantity", new&#91;] { "Quantity must be greater than zero." } }
        });
    }

    // process order
    return Results.Created($"/orders/{order.Id}", order);
});</code></pre>



<p>We can also use Data Annotations manually:</p>



<pre class="wp-block-code"><code>public class OrderDto
{
    &#91;Required]
    public string ProductName { get; set; }

    &#91;Range(1, int.MaxValue)]
    public int Quantity { get; set; }
}</code></pre>



<p class="wp-block-paragraph">Since these aren&#8217;t auto-validated, you validate manually via <code>Validator.TryValidateObject</code>:</p>



<pre class="wp-block-code"><code>app.MapPost("/orders", (OrderDto order) =&gt;
{
    var context = new ValidationContext(order);
    var results = new List&lt;ValidationResult&gt;();

    bool isValid = Validator.TryValidateObject(order, context, results, true);

    if (!isValid)
    {
        var errors = results.ToDictionary(
            r =&gt; r.MemberNames.FirstOrDefault() ?? "",
            r =&gt; new&#91;] { r.ErrorMessage }
        );
        return Results.ValidationProblem(errors);
    }

    return Results.Created($"/orders/{order.Id}", order);
});</code></pre>



<p><b>42. How do you define common metadata for a group of endpoints ?</b></p>
<p>In Minimal APIs, you use <code>MapGroup()</code> to create a route group, and then chain metadata-related extension methods onto the group itself. Any metadata applied to the group is automatically inherited by every endpoint mapped within it.</p>
<p><u>Basic example:</u></p>



<pre class="wp-block-code"><code>var group = app.MapGroup("/api/orders")
                .WithTags("Orders")
                .RequireAuthorization()
                .WithOpenApi();

group.MapGet("/", GetOrders);
group.MapPost("/", CreateOrder);
group.MapDelete("/{id}", DeleteOrder);</code></pre>



<p>Here, all three endpoints automatically get:</p>
<ul>
<li>The /api/orders prefix</li>
<li>The &#8220;Orders&#8221; OpenAPI tag</li>
<li>The authorization requirement</li>
<li>OpenAPI metadata generation</li>
</ul>



<div class="note">Common metadata methods you can apply to a group</div>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Method</th><th>Purpose</th></tr></thead><tbody><tr><td><code>RequireAuthorization()</code></td><td>Applies authorization to all endpoints in the group</td></tr><tr><td><code>AllowAnonymous()</code></td><td>Allows anonymous access for all (can be overridden per endpoint)</td></tr><tr><td><code>WithTags("TagName")</code></td><td>Groups endpoints under a tag in Swagger/OpenAPI</td></tr><tr><td><code>WithOpenApi()</code></td><td>Adds OpenAPI metadata generation</td></tr><tr><td><code>WithMetadata(...)</code></td><td>Attaches arbitrary custom metadata objects</td></tr><tr><td><code>WithSummary()</code> / <code>WithDescription()</code></td><td>Adds OpenAPI documentation text</td></tr><tr><td><code>Produces&lt;T&gt;()</code></td><td>Declares expected response type/content</td></tr><tr><td><code>AddEndpointFilter&lt;T&gt;()</code></td><td>Applies a filter (e.g., validation, logging) to every endpoint in the group</td></tr><tr><td><code>CacheOutput()</code></td><td>Applies output caching policy to the group</td></tr><tr><td><code>RequireRateLimiting("policy")</code></td><td>Applies rate limiting to the group</td></tr><tr><td><code>RequireCors("policyName")</code></td><td>Applies CORS policy to the group</td></tr></tbody></table></div>



<p><u>Example combining several:</u></p>



<pre class="wp-block-code"><code>var group = app.MapGroup("/api/products")
    .RequireAuthorization("AdminOnly")
    .WithTags("Products")
    .AddEndpointFilter&lt;ValidationFilter&lt;ProductDto&gt;&gt;()
    .RequireRateLimiting("fixed")
    .WithOpenApi();

group.MapGet("/", GetProducts);
group.MapPost("/", CreateProduct);</code></pre>



<p><u>Overriding metadata for a specific endpoint within the group:</u></p>
<p>Individual endpoints can still customize or override group-level settings:</p>



<pre class="wp-block-code"><code>group.MapGet("/public", GetPublicProducts)
     .AllowAnonymous();   // overrides the group's RequireAuthorization()

group.MapPost("/", CreateProduct)
     .WithTags("Products", "Write-Operations"); // adds additional tag</code></pre>



<p><u>Nested groups:</u></p>
<p>Groups can also be nested, and metadata compounds down the hierarchy:</p>



<pre class="wp-block-code"><code>var api = app.MapGroup("/api");
var orders = api.MapGroup("/orders").RequireAuthorization();

orders.MapGet("/", GetOrders); // inherits /api/orders + RequireAuthorization</code></pre>



<div class="note">Key interview point to mention</div>



<p><code>MapGroup()</code> returns a <code>RouteGroupBuilder</code>, which implements the same <code>IEndpointConventionBuilder</code> interface as individual route mappings (<code>MapGet</code>, <code>MapPost</code>, etc.). That&#8217;s <em>why</em> all the same fluent extension methods (<code>RequireAuthorization</code>, <code>WithTags</code>, <code>AddEndpointFilter</code>, etc.) work identically whether applied to a single endpoint or an entire group — it&#8217;s a unified builder abstraction, not a special case for groups.</p>
<p><b>43. How do you return different HTTP status codes from a Minimal API ?</b></p>



<p class="wp-block-paragraph">Minimal APIs use the static <strong><code>Results</code></strong> class (or the <code>TypedResults</code> class for a strongly-typed variant) to construct responses with specific HTTP status codes, instead of returning <code>IActionResult</code> types like in MVC controllers.</p>



<p>Common <code>Results</code> methods and their status codes:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Method</th><th>Status Code</th><th>Purpose</th></tr></thead><tbody><tr><td><code>Results.Ok(value)</code></td><td>200</td><td>Success with a body</td></tr><tr><td><code>Results.Created(uri, value)</code></td><td>201</td><td>Resource created</td></tr><tr><td><code>Results.CreatedAtRoute(...)</code></td><td>201</td><td>Created, using a named route to build Location header</td></tr><tr><td><code>Results.Accepted(uri, value)</code></td><td>202</td><td>Accepted for async processing</td></tr><tr><td><code>Results.NoContent()</code></td><td>204</td><td>Success, no body</td></tr><tr><td><code>Results.BadRequest(errors)</code></td><td>400</td><td>Invalid request</td></tr><tr><td><code>Results.Unauthorized()</code></td><td>401</td><td>Not authenticated</td></tr><tr><td><code>Results.Forbid()</code></td><td>403</td><td>Authenticated but not allowed</td></tr><tr><td><code>Results.NotFound()</code></td><td>404</td><td>Resource not found</td></tr><tr><td><code>Results.Conflict()</code></td><td>409</td><td>Conflict (e.g., duplicate resource)</td></tr><tr><td><code>Results.UnprocessableEntity()</code></td><td>422</td><td>Semantic validation error</td></tr><tr><td><code>Results.ValidationProblem(errors)</code></td><td>400</td><td>Structured validation error response</td></tr><tr><td><code>Results.Problem(...)</code></td><td>Configurable (default 500)</td><td>RFC 7807 problem details</td></tr><tr><td><code>Results.StatusCode(code)</code></td><td>Custom</td><td>Any arbitrary status code</td></tr></tbody></table></div>



<p><u>Basic example:</u></p>



<pre class="wp-block-code"><code>app.MapGet("/orders/{id}", (int id, IOrderService service) =&gt;
{
    var order = service.GetById(id);

    if (order is null)
        return Results.NotFound();

    return Results.Ok(order);
});</code></pre>



<p><u>Returning different results based on logic:</u></p>



<pre class="wp-block-code"><code>app.MapPost("/orders", (OrderDto dto, IOrderService service) =&gt;
{
    if (dto.Quantity &lt;= 0)
        return Results.BadRequest("Quantity must be greater than zero.");

    var created = service.CreateOrder(dto);
    return Results.Created($"/orders/{created.Id}", created);
});</code></pre>



<p><u>Using TypedResults (strongly-typed, better for OpenAPI + testability):</u></p>
<p>TypedResults is preferred in .NET 7+ because it improves OpenAPI schema generation and makes unit testing easier (you get compile-time type checking instead of the generic IResult).</p>



<pre class="wp-block-code"><code>app.MapGet("/orders/{id}", Results&lt;Ok&lt;Order&gt;, NotFound&gt; (int id, IOrderService service) =&gt;
{
    var order = service.GetById(id);

    return order is null
        ? TypedResults.NotFound()
        : TypedResults.Ok(order);
});</code></pre>



<p>Here, the Results<Ok<Order>, NotFound> return type explicitly documents all possible response types for the endpoint — this metadata flows automatically into OpenAPI/Swagger without needing .Produces<T>() calls.</p>
<p><u>Multiple possible outcomes example:</u></p>



<pre class="wp-block-code"><code>app.MapPost("/orders", Results&lt;Created&lt;Order&gt;, BadRequest&lt;string&gt;&gt; (OrderDto dto, IOrderService service) =&gt;
{
    if (dto.Quantity &lt;= 0)
        return TypedResults.BadRequest("Invalid quantity.");

    var order = service.CreateOrder(dto);
    return TypedResults.Created($"/orders/{order.Id}", order);
});</code></pre>



<p><u>Custom status code:</u></p>



<pre class="wp-block-code"><code>app.MapGet("/legacy", () =&gt; Results.StatusCode(410)); // 410 Gone</code></pre>



<p><u>Key interview point to mention:</u></p>
<p>Prefer TypedResults over Results when possible — it:</p>
<ul>
<li>Provides compile-time safety over which result types an endpoint can return.</li>
<li>Automatically generates accurate OpenAPI metadata (status codes + response types) without manual .Produces<T>() annotations.</li>
<li>Makes endpoints easier to unit test since you assert on concrete types instead of the generic IResult interface.</li>
</ul>
<p>Good soundbite: &#8220;Results gives you IResult; TypedResults gives you the same functionality but with concrete types, which pays off in better OpenAPI docs and more testable, compile-time–checked endpoints.&#8221;</p>



<p><b>44. What are endpoint filters and how are endpoint filters different from middleware ?</b></p>
<p>Endpoint filters (IEndpointFilter) are a Minimal API feature (introduced in .NET 7) that let you run logic before and/or after a specific endpoint handler executes — without cluttering the handler itself. They give you access to the strongly-typed route handler arguments, which middleware cannot see.</p>
<p><u>Basic example:</u></p>



<pre class="wp-block-code"><code>public class LoggingFilter : IEndpointFilter
{
    public async ValueTask&lt;object?&gt; InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        Console.WriteLine($"Handling request: {context.HttpContext.Request.Path}");

        var result = await next(context); // call the next filter / the handler itself

        Console.WriteLine($"Response generated: {result}");

        return result;
    }
}</code></pre>



<p class="wp-block-paragraph">app.MapGet(&#8220;/orders/{id}&#8221;, (int id) =&gt; $&#8221;Order {id}&#8221;)<br>.AddEndpointFilter();</p>



<p><u>Applying multiple filters (they run in order, like a pipeline):</u></p>



<pre class="wp-block-code"><code>app.MapPost("/orders", (OrderDto order) =&gt; Results.Created("/orders/1", order))
   .AddEndpointFilter&lt;ValidationFilter&lt;OrderDto&gt;&gt;()
   .AddEndpointFilter&lt;LoggingFilter&gt;();</code></pre>



<p><u>Inline filter (lambda-based, for quick logic):</u></p>



<pre class="wp-block-code"><code>app.MapGet("/orders/{id}", (int id) =&gt; $"Order {id}")
   .AddEndpointFilter(async (context, next) =&gt;
   {
       var id = context.GetArgument&lt;int&gt;(0);
       if (id &lt;= 0)
           return Results.BadRequest("Invalid ID");

       return await next(context);
   });</code></pre>



<p><u>Short-circuiting:</u></p>
<p>Just like middleware, a filter can choose not to call next(), short-circuiting the pipeline and returning immediately (e.g., failed validation, unauthorized access).</p>



<h3>Key Differences: Endpoint Filters vs Middleware</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Aspect</th><th>Middleware</th><th>Endpoint Filters</th></tr></thead><tbody><tr><td><strong>Scope</strong></td><td>Applies globally to the whole request pipeline (or conditionally via <code>app.Map</code>/<code>UseWhen</code>)</td><td>Applies to a <strong>specific endpoint or group</strong></td></tr><tr><td><strong>Registration</strong></td><td><code>app.Use...()</code> in <code>Program.cs</code>, order matters globally</td><td><code>.AddEndpointFilter&lt;T&gt;()</code> chained on a specific route/group</td></tr><tr><td><strong>Access to route data / typed arguments</strong></td><td>No direct access to strongly-typed handler parameters (works with raw <code>HttpContext</code>)</td><td>Full access to handler arguments via <code>context.Arguments</code> / <code>context.GetArgument&lt;T&gt;()</code></td></tr><tr><td><strong>Runs relative to routing</strong></td><td>Runs <strong>before routing resolves the endpoint</strong> (in general middleware) or after, depending on pipeline position</td><td>Runs <strong>after the endpoint has been matched</strong>, right around the handler invocation</td></tr><tr><td><strong>Awareness of Minimal API semantics</strong></td><td>Generic — works the same regardless of framework (MVC, Minimal API, gRPC, etc.)</td><td>Minimal API–specific — tightly coupled to endpoint handler signature and return value</td></tr><tr><td><strong>Use case fit</strong></td><td>Cross-cutting, pipeline-wide concerns: authentication, CORS, exception handling, routing, static files</td><td>Endpoint-specific or group-specific concerns: validation, argument transformation, per-route logging/auditing</td></tr><tr><td><strong>Return value visibility</strong></td><td>Operates on <code>HttpContext</code> directly; doesn&#8217;t see the handler&#8217;s return object in a typed way</td><td>Can inspect <strong>and modify</strong> the actual result object returned by the handler before it&#8217;s sent</td></tr></tbody></table></figure>



<p>Conceptual analogy:</p>



<ul class="wp-block-list">
<li><strong>Middleware</strong> = concentric layers wrapping the <em>entire request pipeline</em> (onion model) — it doesn&#8217;t know or care which endpoint eventually handles the request.</li>



<li><strong>Endpoint filters</strong> = a pipeline <em>scoped to one endpoint&#8217;s invocation</em> — closer to &#8220;method interceptors&#8221; or &#8220;action filters&#8221; in MVC (similar to <code>IActionFilter</code>), but for Minimal APIs.</li>
</ul>



<p>When to use which:</p>



<ul class="wp-block-list">
<li>Use <strong>middleware</strong> for concerns that apply broadly across many/most routes regardless of what they do: authentication, HTTPS redirection, CORS, global exception handling, response compression.</li>



<li>Use <strong>endpoint filters</strong> for concerns tied to the <strong>specific inputs/outputs of certain endpoints</strong>: request validation against a specific DTO, argument logging, transforming a specific endpoint&#8217;s result, applying business rules only relevant to a subset of routes.</li>
</ul>



<h2>Entity Framework Core Interview Questions</h2>
<p>EF Core is Microsoft&#8217;s modern, open-source, cross-platform Object-Relational Mapper (ORM) for .NET. It lets you work with a database using .NET objects (classes) instead of writing raw SQL — you query and manipulate data using C# and LINQ, and EF Core translates that into SQL behind the scenes.</p>
<p>EF Core questions are less about memorizing method names and more a filter for practical, production-grade .NET experience — efficient querying, understanding abstractions instead of just using them, and making sensible architecture calls. That&#8217;s exactly why they show up so often in interviews.</p>
<h3>Why EF Core Interview Questions Matter:</h3>
<p>EF Core comes up constantly in .NET interviews because it sits at the intersection of a few things employers actually care about in day-to-day work:</p>
<ul>
<li>It&#8217;s the default data access story for .NET</li>
<li>It reveals whether you understand what&#8217;s happening under the hood</li>
<li>Performance and scaling questions live here</li>
<li>It tests architectural judgment</li>
<li>Migrations = real-world team workflow</li>
</ul>
<p><b>45. what is DbContext ? What happens internally when SaveChanges() is called ?</b></p>



<p class="wp-block-paragraph"><code>DbContext</code> is the <strong>primary class in EF Core</strong> that represents a session with the database. It&#8217;s the bridge between your C# objects and the underlying database, responsible for:</p>



<ul class="wp-block-list">
<li><strong>Querying</strong> the database (via <code>DbSet&lt;T&gt;</code> properties)</li>



<li><strong>Tracking changes</strong> made to entity instances in memory</li>



<li><strong>Persisting changes</strong> back to the database (<code>SaveChanges()</code>)</li>



<li><strong>Managing the connection</strong> lifecycle</li>



<li><strong>Configuring the model</strong> (relationships, keys, constraints) via <code>OnModelCreating</code></li>



<li><strong>Caching a first-level &#8220;identity map&#8221;</strong> — within one <code>DbContext</code> instance, querying the same entity twice returns the <em>same</em> object reference</li>
</ul>



<p>Basic shape:</p>



<pre class="wp-block-code"><code>public class AppDbContext : DbContext
{
    public DbSet&lt;Order&gt; Orders { get; set; }
    public DbSet&lt;Customer&gt; Customers { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity&lt;Order&gt;()
            .HasOne(o =&gt; o.Customer)
            .WithMany(c =&gt; c.Orders)
            .HasForeignKey(o =&gt; o.CustomerId);
    }
}</code></pre>



<p>Key characteristics:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Aspect</th><th>Detail</th></tr></thead><tbody><tr><td><strong>Lifetime</strong></td><td>Should be <strong>short-lived</strong> — typically scoped to a single unit of work (one HTTP request in a web app). Registered as <code>Scoped</code> in DI by default via <code>AddDbContext&lt;T&gt;()</code>.</td></tr><tr><td><strong>Not thread-safe</strong></td><td>A single <code>DbContext</code> instance must not be used concurrently across threads.</td></tr><tr><td><strong>Unit of Work + Repository pattern</strong></td><td><code>DbContext</code> itself implements the Unit of Work pattern; <code>DbSet&lt;T&gt;</code> acts like a Repository.</td></tr><tr><td><strong>Holds a Change Tracker</strong></td><td>Every entity it retrieves or you attach gets a tracked &#8220;entry&#8221; with a state (<code>Added</code>, <code>Modified</code>, <code>Deleted</code>, <code>Unchanged</code>, <code>Detached</code>).</td></tr></tbody></table></div>



<h3>What Happens Internally When <code>SaveChanges()</code> Is Called?</h3>



<p class="wp-block-paragraph">This is the meatier part of the question — here&#8217;s the internal sequence:</p>



<p><u>a. DetectChanges() runs</u></p>



<p class="wp-block-paragraph">EF Core walks through every tracked entity and compares its <strong>current property values</strong> against the <strong>original snapshot values</strong> captured when the entity was first queried/attached. This is how it figures out what actually changed without you telling it explicitly.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Note: With <code>ChangeTracker.AutoDetectChangesEnabled</code>, this normally happens automatically before <code>SaveChanges()</code>, before LINQ queries, and at a few other trigger points. You can disable it and call <code>ChangeTracker.DetectChanges()</code> manually for performance-sensitive bulk scenarios.</p>
</blockquote>



<p><u>b. Each tracked entity&#8217;s state is evaluated</b></u>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>State</th><th>Meaning</th><th>Resulting SQL</th></tr></thead><tbody><tr><td><code>Added</code></td><td>New entity, not yet in the DB</td><td><code>INSERT</code></td></tr><tr><td><code>Modified</code></td><td>Existing entity with changed property values</td><td><code>UPDATE</code></td></tr><tr><td><code>Deleted</code></td><td>Marked for removal</td><td><code>DELETE</code></td></tr><tr><td><code>Unchanged</code></td><td>No changes detected</td><td>No SQL generated</td></tr></tbody></table></div>



<p><u>c. A change set / execution plan is built</u></p>



<p class="wp-block-paragraph">EF Core groups the pending <code>Added</code>/<code>Modified</code>/<code>Deleted</code> entries and determines the <strong>correct order of operations</strong> — respecting foreign key dependencies. For example, a parent entity must be inserted <em>before</em> a dependent child entity that references its generated key (this matters a lot with auto-generated identity/primary keys).</p>



<p><u>d. A database transaction is started (implicitly)</u></p>



<p class="wp-block-paragraph">If you didn&#8217;t start one manually, EF Core wraps all the generated SQL statements from this <code>SaveChanges()</code> call in an <strong>implicit transaction</strong>. This guarantees atomicity — either all changes commit, or none do, if any statement fails.</p>



<p><u>e. SQL statements are generated and sent to the database</u></p>



<ul class="wp-block-list">
<li>By default, EF Core batches multiple <code>INSERT</code>/<code>UPDATE</code>/<code>DELETE</code> statements into fewer round trips where the provider supports it (SQL Server, for example, batches statements).</li>



<li>Parameterized SQL is used (protects against SQL injection and enables plan caching).</li>
</ul>



<p><u>f. Generated keys are propagated back</u></p>



<p class="wp-block-paragraph">For <code>Added</code> entities with database-generated keys (identity columns), EF Core reads back the newly generated primary key values from the database and updates the in-memory entity objects with them.</p>



<p><u>g. Concurrency tokens are checked (if configured)</u></p>



<p class="wp-block-paragraph">If the entity has a concurrency token (e.g., <code>[Timestamp]</code>/<code>RowVersion</code> column, or a property marked <code>IsConcurrencyToken</code>), the generated <code>UPDATE</code>/<code>DELETE</code> includes a <code>WHERE</code> clause checking the original value. If <strong>zero rows</strong> are affected (meaning someone else already changed/deleted the row), EF Core throws a <code>DbUpdateConcurrencyException</code>.</p>



<p><u>h. Transaction commits (or rolls back on failure)</u></p>



<p class="wp-block-paragraph">If everything succeeds, the transaction commits. If any statement fails (constraint violation, concurrency conflict, connection issue), the transaction rolls back and EF Core throws (<code>DbUpdateException</code> or <code>DbUpdateConcurrencyException</code>).</p>



<p><u>i. Change tracker state resets</u></p>



<p class="wp-block-paragraph">After a successful save:</p>



<ul class="wp-block-list">
<li><code>Added</code> entities become <code>Unchanged</code> (now that they exist in the DB)</li>



<li><code>Modified</code> entities become <code>Unchanged</code></li>



<li><code>Deleted</code> entities are <strong>detached</strong> from the context entirely</li>



<li>The &#8220;original values&#8221; snapshot is refreshed to match current values, so the next <code>SaveChanges()</code> only picks up <em>new</em> changes</li>
</ul>



<p><u>j. <code>SaveChanges()</code> returns an <code>int</code></u></p>



<p>Specifically, the <strong>number of rows affected</strong> in the database (or state entries written) — useful as a lightweight way to confirm something actually happened.</p>
<p><u>Visual summary:</u></p>



<pre class="wp-block-code"><code>SaveChanges() called
        ↓
DetectChanges() — diff current vs. original snapshot values
        ↓
Determine entity states (Added / Modified / Deleted)
        ↓
Order operations respecting FK dependencies
        ↓
Begin implicit transaction (if none active)
        ↓
Generate + execute parameterized SQL (batched where possible)
        ↓
Read back DB-generated keys → update tracked entities
        ↓
Check concurrency tokens (throw if conflict)
        ↓
Commit transaction (or rollback on error)
        ↓
Reset entity states to Unchanged / detach Deleted entities
        ↓
Return count of affected rows</code></pre>



<p><b>46. What is the Change Tracker ? What are entity states in EF Core ?</b></p>
<p>The Change Tracker (ChangeTracker) is a component owned by every DbContext instance that monitors the state of entity instances the context knows about — whether they were retrieved from the database, added, or manually attached. Its job is to figure out what changed so that SaveChanges() knows exactly which INSERT/UPDATE/DELETE statements to generate.</p>
<p>You can access it directly:</p>



<pre class="wp-block-code"><code>var entries = context.ChangeTracker.Entries();

foreach (var entry in entries)
{
    Console.WriteLine($"{entry.Entity.GetType().Name} — {entry.State}");
}</code></pre>



<p>How it works internally:</p>



<ul class="wp-block-list">
<li>When an entity is <strong>queried</strong>, EF Core takes a <strong>snapshot of its original property values</strong> at that moment and starts tracking it.</li>



<li>As you mutate properties on that entity in memory, the object itself changes — but the <em>original snapshot</em> stays frozen.</li>



<li>When <code>DetectChanges()</code> runs (automatically before <code>SaveChanges()</code>, before queries, etc.), EF Core compares <strong>current values vs. original snapshot values</strong> property-by-property to determine what&#8217;s actually different.</li>



<li>Based on that diff, it assigns/updates the entity&#8217;s <strong>state</strong>.</li>
</ul>



<p>Change tracking strategies:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Strategy</th><th>How it works</th></tr></thead><tbody><tr><td><strong>Snapshot tracking</strong> (default)</td><td>EF Core stores a full snapshot of original values and diffs against it during <code>DetectChanges()</code></td></tr><tr><td><strong>Notification tracking</strong></td><td>Entities implement <code>INotifyPropertyChanged</code>; changes are detected immediately as they happen, no need to call <code>DetectChanges()</code> — more efficient for large object graphs</td></tr></tbody></table></div>



<p>Accessing/manipulating tracked entries directly:</p>



<pre class="wp-block-code"><code>var entry = context.Entry(order);

Console.WriteLine(entry.State); // e.g. Modified

// Check specific property changes
var originalQty = entry.Property(o =&gt; o.Quantity).OriginalValue;
var currentQty = entry.Property(o =&gt; o.Quantity).CurrentValue;
bool isModified = entry.Property(o =&gt; o.Quantity).IsModified;</code></pre>



<h3 >Entity States in EF Core</h3>



<p class="wp-block-paragraph">Every tracked entity has an <code>EntityState</code> (an enum) at any given time:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>State</th><th>Meaning</th><th>SQL generated on <code>SaveChanges()</code></th></tr></thead><tbody><tr><td><strong><code>Added</code></strong></td><td>Entity is new; doesn&#8217;t exist in the DB yet</td><td><code>INSERT</code></td></tr><tr><td><strong><code>Unchanged</code></strong></td><td>Entity exists in DB and no properties have changed since it was loaded/last saved</td><td>None</td></tr><tr><td><strong><code>Modified</code></strong></td><td>Entity exists in DB, and one or more property values differ from the original snapshot</td><td><code>UPDATE</code></td></tr><tr><td><strong><code>Deleted</code></strong></td><td>Entity exists in DB but is marked for removal</td><td><code>DELETE</code></td></tr><tr><td><strong><code>Detached</code></strong></td><td>Entity is <strong>not tracked</strong> by this context at all — EF Core knows nothing about it</td><td>None (until attached/added)</td></tr></tbody></table></div>



<p>How entities transition between states:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Detached → Added
var newOrder = new Order { ProductName = &quot;Widget&quot; };
context.Orders.Add(newOrder);           // state: Added

// Query result → Unchanged (automatically tracked)
var order = context.Orders.First();     // state: Unchanged

// Unchanged → Modified (automatic, via change detection)
order.Quantity = 99;                    // still Unchanged in memory...
context.ChangeTracker.DetectChanges();  // ...now becomes Modified

// Unchanged/Modified → Deleted
context.Orders.Remove(order);           // state: Deleted

// After successful SaveChanges()
// Added    → Unchanged
// Modified → Unchanged
// Deleted  → Detached (removed from tracker entirely)
</pre></div>


<p><b>47. What is DbSet&lt;t> ?</t></b></p>
<p><code>DbSet&lt;T&gt;</code> is a class in EF Core that represents a collection of entities of a given type that can be queried and manipulated against the database — conceptually, it maps to a table (or a queryable view/set of rows) in the underlying database.</p>
<p>It&#8217;s exposed as a property on your DbContext, and it&#8217;s the main entry point for interacting with a specific entity type.</p>



<pre class="wp-block-code"><code>public class AppDbContext : DbContext
{
    public DbSet&lt;Order&gt; Orders { get; set; }
    public DbSet&lt;Customer&gt; Customers { get; set; }
}</code></pre>



<p>What <code>DbSet&lt;T&gt;</code> gives you:</p>



<p>a. Querying (it implements <code>IQueryable&lt;T&gt;</code>)</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var bigOrders = context.Orders
    .Where(o =&gt; o.Quantity &gt; 10)
    .OrderBy(o =&gt; o.ProductName)
    .ToList();
</pre></div>


<p class="wp-block-paragraph">Because <code>DbSet&lt;T&gt;</code> implements <code>IQueryable&lt;T&gt;</code>, LINQ expressions built against it are <strong>translated into SQL</strong> and executed on the database — not run in memory (this matters a lot; see <code>IQueryable</code> vs <code>IEnumerable</code> below).</p>



<p class="wp-block-paragraph">b. Adding entities</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
context.Orders.Add(new Order { ProductName = &quot;Widget&quot;, Quantity = 5 });
context.Orders.AddRange(order1, order2, order3);
</pre></div>


<p class="wp-block-paragraph">Marks the entity as <code>Added</code> in the Change Tracker — it will generate an <code>INSERT</code> on <code>SaveChanges()</code>.</p>



<p>c. Removing entities</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
context.Orders.Remove(order);
context.Orders.RemoveRange(order1, order2);
</pre></div>


<p class="wp-block-paragraph">Marks the entity as <code>Deleted</code> — generates a <code>DELETE</code> on <code>SaveChanges()</code>.</p>



<p class="wp-block-paragraph">d. Updating entities</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
context.Orders.Update(order);
</pre></div>


<p class="wp-block-paragraph">Marks the entire entity graph as <code>Modified</code> (used mainly in disconnected scenarios where the entity wasn&#8217;t already tracked).</p>



<p class="wp-block-paragraph">e. Finding by primary key</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var order = context.Orders.Find(5);
// or async
var order = await context.Orders.FindAsync(5);
</pre></div>


<p><code>Find</code>/<code>FindAsync</code> first checks the <strong>Change Tracker&#8217;s local cache</strong> (identity map) before hitting the database — if the entity with that key is already tracked in memory, it&#8217;s returned without a database round trip.</p>
<p><b>48. What is change tracking ? What is AsNoTracking()?</b></p>



<p class="wp-block-paragraph"><strong>Change Tracking</strong> is the mechanism by which EF Core&#8217;s <code>DbContext</code> monitors entities that have been loaded (via queries) or attached to it, so it knows what changes have been made in memory and can generate the correct SQL (<code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>) when <code>SaveChanges()</code> is called.</p>



<p><u>How it works:</u></p>



<ol class="wp-block-list">
<li>When you query entities using a <code>DbContext</code> (e.g., <code>context.Employees.ToList()</code>), EF Core creates a <strong>snapshot</strong> of each entity&#8217;s original values and starts tracking it.</li>



<li>Each tracked entity has an internal <strong>state</strong>, represented by the <code>EntityState</code> enum:
<ul class="wp-block-list">
<li><code>Added</code> – new entity, will be inserted</li>



<li><code>Unchanged</code> – no modifications since it was loaded</li>



<li><code>Modified</code> – one or more properties changed</li>



<li><code>Deleted</code> – marked for deletion</li>



<li><code>Detached</code> – not being tracked</li>
</ul>
</li>



<li>When you modify a property on a tracked entity, EF Core compares the current value to the snapshot and marks the entity (and the specific properties) as <code>Modified</code>.</li>



<li>When <code>SaveChanges()</code> is called, EF Core inspects the tracked entities&#8217; states and generates the appropriate SQL statements only for what actually changed.</li>
</ol>



<pre class="wp-block-code"><code>var employee = context.Employees.First(e =&gt; e.Id == 1); // tracked
employee.Salary = 50000; // EF detects this change automatically

context.SaveChanges(); // generates UPDATE ... SET Salary = 50000 WHERE Id = 1</code></pre>



<p class="wp-block-paragraph">You didn&#8217;t have to explicitly call <code>Update()</code> — EF Core detected the change itself via tracking.</p>



<p><u>Why it matters (interview angle):</u></p>



<ul class="wp-block-list">
<li>It enables the &#8220;just modify the object&#8221; programming model instead of manually writing UPDATE statements.</li>



<li>It has a <strong>performance cost</strong>: EF Core keeps snapshots and does change detection (<code>DetectChanges()</code>), which takes memory and CPU, especially with large result sets.</li>



<li>It&#8217;s central to concepts like the <strong>Unit of Work</strong> pattern that <code>DbContext</code> implements.</li>
</ul>



<h3>AsNoTracking()</h3>
<p>AsNoTracking() is a query extension method that tells EF Core not to track the entities returned by that query. The entities are returned as plain objects with no snapshot kept, and no EntityState is maintained for them.</p>



<pre class="wp-block-code"><code>var employees = context.Employees
    .AsNoTracking()
    .Where(e =&gt; e.DepartmentId == 3)
    .ToList();</code></pre>



<p><u>Key characteristics:</u></p>



<ul class="wp-block-list">
<li><strong>Read-only intent</strong>: Since there&#8217;s no tracking, changes to these entities won&#8217;t be detected or persisted by <code>SaveChanges()</code> unless you explicitly attach them again.</li>



<li><strong>Performance benefit</strong>: Skips the overhead of creating snapshots and change-detection, making queries faster and more memory-efficient — especially valuable for:
<ul class="wp-block-list">
<li>Reporting queries</li>



<li>API GET endpoints that just return data</li>



<li>Large result sets</li>
</ul>
</li>



<li>Can be applied per-query (<code>.AsNoTracking()</code>) or set as a context-wide default:</li>
</ul>



<pre class="wp-block-code"><code>context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;</code></pre>



<p>There&#8217;s also <code>AsNoTrackingWithIdentityResolution()</code>, which avoids full tracking overhead but still ensures that the same logical entity (same key) maps to the same object instance within that query&#8217;s results — useful when a query has duplicate/related rows for the same entity.</p>
<p><b>49. Explain Added, Modified, Deleted, Unchanged, and Detached</b></p>
<p>Every entity that EF Core knows about (tracked entities) has a state, represented by the EntityState enum. This state tells SaveChanges() exactly what SQL operation to generate for that entity.</p>



<pre class="wp-block-code"><code>public enum EntityState
{
    Detached,
    Unchanged,
    Deleted,
    Modified,
    Added
}</code></pre>



<p class="wp-block-paragraph">You can inspect it at any time:</p>



<pre class="wp-block-code"><code>var state = context.Entry(employee).State;</code></pre>



<h3>a. Detached</h3>
<p>The entity exists as a .NET object, but the DbContext is not tracking it at all. EF knows nothing about it — no snapshot, no state management.</p>



<pre class="wp-block-code"><code>var employee = new Employee { Name = "John" }; // Detached — just created, not added to context</code></pre>



<p>An entity also becomes <code>Detached</code> again if you explicitly call <code>context.Entry(entity).State = EntityState.Detached</code>, or after the context is disposed.</p>
<h3>b. Added</h3>
<p>The entity is new and tracked, and will be inserted into the database on the next <span class="code">SaveChanges()</span>. Its primary key (if database-generated) is typically not yet set to a real value.</p>



<pre class="wp-block-code"><code>context.Employees.Add(employee); // state becomes Added
context.SaveChanges(); // generates INSERT</code></pre>



<h3>c. Unchanged</h3>
<p>The entity is tracked, and its current property values match the original snapshot EF Core took when it was loaded. No SQL is generated for this entity on <span class="code">SaveChanges()</span>.</p>



<pre class="wp-block-code"><code>var employee = context.Employees.First(); // state is Unchanged right after loading</code></pre>



<h3>d. Modified</h3>
<p>The entity is tracked, and at least one property value differs from its original snapshot. EF Core tracks changes at the property level, not just entity level — so it knows exactly which columns to include in the UPDATE statement.</p>



<pre class="wp-block-code"><code>employee.Salary = 60000; // state becomes Modified
context.SaveChanges(); // generates UPDATE ... SET Salary = 60000 WHERE Id = ...</code></pre>



<h3>e. Deleted</h3>
<p>The entity is tracked and marked for removal. On SaveChanges(), EF Core generates a DELETE statement using the entity&#8217;s key.</p>



<pre class="wp-block-code"><code>context.Employees.Remove(employee); // state becomes Deleted
context.SaveChanges(); // generates DELETE FROM Employees WHERE Id = ...</code></pre>



<p><u>State transition summary:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Action</th><th>Resulting State</th></tr></thead><tbody><tr><td><code>new Employee()</code></td><td>Detached</td></tr><tr><td><code>context.Add(entity)</code></td><td>Added</td></tr><tr><td>Query result (<code>ToList()</code>, <code>First()</code>, etc.)</td><td>Unchanged</td></tr><tr><td>Modify a property on a tracked entity</td><td>Modified</td></tr><tr><td><code>context.Remove(entity)</code></td><td>Deleted</td></tr><tr><td><code>SaveChanges()</code> completes</td><td>Added/Modified → Unchanged; Deleted → Detached</td></tr></tbody></table></div>
<p><b>50. What is eager loading, lazy loading and explicit loading ?</b></p>
<p>EF Core offers three strategies for loading related (navigation property) data. This is a very common interview question because it tests both API knowledge and performance judgment.</p>
<h3>Eager Loading</h3>
<p>Related data is loaded upfront, as part of the same query, using <span class="code">Include()</span> (and <span class="code">ThenInclude()</span> for deeper levels).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var employees = context.Employees
    .Include(e =&gt; e.Department)
    .ThenInclude(d =&gt; d.Location)
    .Include(e =&gt; e.Projects)
    .ToList();
</pre></div>


<p>This generates a single SQL query (typically using JOINs) that pulls employees along with their departments, locations, and projects all at once.</p>
<p><u>When to use:</u></p>
<ul>
<li>You know upfront you&#8217;ll need the related data.</li>
<li>Avoids the N+1 query problem (see below).</li>
</ul>
<p><u>Downside:</u></p>
<ul>
<li>Over-fetching if you don&#8217;t actually need the related data every time.</li>
<li>Multiple Include() calls with collections can cause a cartesian explosion — the result set balloons because of multiple JOINs multiplying row counts. EF Core mitigates this using &#8220;split queries&#8221; (see below).</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Split query — runs separate SQL queries instead of one big JOIN, avoiding cartesian explosion
var employees = context.Employees
    .Include(e =&gt; e.Projects)
    .Include(e =&gt; e.Certifications)
    .AsSplitQuery()
    .ToList();
</pre></div>


<h3>Lazy Loading</h3>
<p>Related data is loaded automatically, on-demand, the moment you access a navigation property — not when the initial query runs.</p>
<p><u>To enable it:</u></p>
<ul>
<li>Install Microsoft.EntityFrameworkCore.Proxies</li>
<li>Enable it in DbContext configuration: optionsBuilder.UseLazyLoadingProxies()</li>
<li>Mark navigation properties as virtual</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Department Department { get; set; } // must be virtual
}

var employee = context.Employees.First(); // only Employee is loaded
var deptName = employee.Department.Name; // triggers a SEPARATE query here, behind the scenes
</pre></div>


<p class="wp-block-paragraph"><u>When to use:</u></p>



<ul class="wp-block-list">
<li>Rarely recommended for anything but small, exploratory scenarios.</li>
</ul>



<p class="wp-block-paragraph"><u>Downside (important interview point):</u></p>



<ul class="wp-block-list">
<li>Causes the infamous <strong>N+1 query problem</strong> — if you loop over 100 employees and access <code>.Department</code> on each, that&#8217;s 1 query for employees + 100 separate queries for departments.</li>
</ul>



<pre class="wp-block-code"><code>foreach (var emp in employees) // 1 query already ran
{
    Console.WriteLine(emp.Department.Name); // N additional queries!
}</code></pre>



<ul class="wp-block-list">
<li>Requires proxies and virtual properties, which some consider an anti-pattern (breaks POCO purity, has gotchas with sealed classes/structs).</li>



<li>Easy to accidentally trigger from serialization frameworks (e.g., a JSON serializer touching every navigation property).</li>
</ul>



<h3>Explicit Loading</h3>
<p>You load the initial entity without related data, then explicitly and deliberately trigger loading of specific navigation properties when you choose to, using <code>context.Entry()</code>.</p>



<pre class="wp-block-code"><code>var employee = context.Employees.First(); // just Employee, nothing else

// Explicitly load a reference navigation property
context.Entry(employee).Reference(e =&gt; e.Department).Load();

// Explicitly load a collection navigation property
context.Entry(employee).Collection(e =&gt; e.Projects).Load();

// You can even filter what gets loaded
context.Entry(employee).Collection(e =&gt; e.Projects)
    .Query()
    .Where(p =&gt; p.IsActive)
    .Load();</code></pre>



<p class="wp-block-paragraph"><u>When to use:</u></p>



<ul class="wp-block-list">
<li>You need related data only conditionally (e.g., inside an <code>if</code> block, based on business logic).</li>



<li>You want the control of lazy loading&#8217;s &#8220;load only if needed&#8221; without the hidden magic and N+1 risk of automatic lazy loading — the loading call is visible in the code.</li>
</ul>



<p><u>Comparison Table:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Strategy</th><th>When data loads</th><th>Query count</th><th>Main risk</th></tr></thead><tbody><tr><td><strong>Eager</strong> (<code>Include</code>)</td><td>Upfront, with main query</td><td>1 (or more with split query)</td><td>Over-fetching, cartesian explosion</td></tr><tr><td><strong>Lazy</strong></td><td>On property access (automatic)</td><td>1 + N (per access)</td><td>N+1 problem, hidden queries</td></tr><tr><td><strong>Explicit</strong></td><td>On-demand, but manually triggered</td><td>1 + however many you explicitly call</td><td>Still N+1 if called in a loop without care</td></tr></tbody></table></div>



<p><b>51. What is the difference between Include() and ThenInclude() ?</b></p>
<p>Both are used for eager loading of related data, but they operate at different levels of the object graph.</p>
<h3>Include()</h3>
<p>Loads a navigation property directly on the entity you&#8217;re querying (first level of relation).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var employees = context.Employees
    .Include(e =&gt; e.Department)
    .ToList();
</pre></div>


<p class="wp-block-paragraph">This loads each <code>Employee</code> along with its direct <code>Department</code>.</p>



<p class="wp-block-paragraph">You can also chain multiple <code>Include()</code> calls to load several <em>sibling</em> (first-level) navigation properties independently:</p>



<pre class="wp-block-code"><code>var employees = context.Employees
    .Include(e =&gt; e.Department)
    .Include(e =&gt; e.Projects)
    .ToList();</code></pre>



<p>This loads Department and Projects, both directly related to Employee.</p>
<h3>ThenInclude()</h3>
<p>Loads a navigation property on an entity that was just included — i.e., it goes one level deeper into the object graph, continuing from the previous Include()/ThenInclude().</p>



<pre class="wp-block-code"><code>var employees = context.Employees
    .Include(e =&gt; e.Department)
    .ThenInclude(d =&gt; d.Location)
    .ToList();</code></pre>



<p>This loads Employee → Department → Location. Without ThenInclude(), there&#8217;s no way to express &#8220;go one level deeper&#8221; — Include() alone only knows about the root entity&#8217;s navigation properties.</p>
<p><u>Combining them — branching the graph:</u></p>
<p>You can mix multiple Include/ThenInclude chains to load a complex graph. Each new Include() call starts a new branch from the root entity:</p>



<pre class="wp-block-code"><code>var employees = context.Employees
    .Include(e =&gt; e.Department)
        .ThenInclude(d =&gt; d.Location)
    .Include(e =&gt; e.Projects)
        .ThenInclude(p =&gt; p.Client)
    .ToList();</code></pre>



<p class="wp-block-paragraph">This produces:</p>



<pre class="wp-block-code"><code>Employee
 ├── Department
 │     └── Location
 └── Projects
       └── Client</code></pre>



<p>The second <code>Include(e => e.Projects)</code> resets the &#8220;current level&#8221; back to the root <code>Employee</code>, so the interviewer should know that <code>ThenInclude()</code> always continues from the <em>immediately preceding</em> <code>Include</code>/<code>ThenInclude</code> in that chain — not from wherever the chain &#8220;ended up.&#8221;</p>
<p><u>Multiple levels deep:</u></p>
<p>You can chain ThenInclude() multiple times to go arbitrarily deep:</p>



<pre class="wp-block-code"><code>var employees = context.Employees
    .Include(e =&gt; e.Department)
        .ThenInclude(d =&gt; d.Location)
            .ThenInclude(l =&gt; l.Country)
    .ToList();</code></pre>



<p>Employee → Department → Location → Country</p>
<div class="starBlock">Key distinction (interview soundbite) &#8211; Include() specifies a navigation property on the root entity type being queried. ThenInclude() specifies a navigation property on the entity that was just loaded by the previous Include or ThenInclude call — it lets you traverse multiple levels of relationships in a single eager-loading query.</div>
<p><u>A gotcha worth mentioning:</u></p>
<p>If you need to include a navigation property on a collection&#8217;s element type (e.g., a collection navigation on each item inside another collection), ThenInclude() handles that too — EF Core infers you&#8217;re operating on the element type of the enclosing collection:</p>



<pre class="wp-block-code"><code>context.Departments
    .Include(d =&gt; d.Employees)
        .ThenInclude(e =&gt; e.Projects)
    .ToList();</code></pre>



<p>This loads all <code>Employees</code> for each <code>Department</code>, and all <code>Projects</code> for each of those <code>Employees</code> — correctly handling the one-to-many-to-many nesting.</p>
<p><b>52. What is the difference between EnsureCreated() and EF Core Migrations ?</b></p>
<p>Both can be used to get a database schema in place from your EF Core model, but they&#8217;re designed for very different scenarios and are not compatible with each other.</p>
<h3>Database.EnsureCreated()</h3>



<pre class="wp-block-code"><code>context.Database.EnsureCreated();</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What it does:</span></p>



<ul class="wp-block-list">
<li>Checks if the database exists. If not, creates it.</li>



<li>Creates the schema (tables, columns, keys, indexes) directly from the <strong>current state of your EF Core model</strong> — in one shot.</li>



<li>If the database already exists, it does <strong>nothing</strong> (doesn&#8217;t check if the schema matches the model).</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Key characteristics:</span></p>



<ul class="wp-block-list">
<li>Does <strong>not</strong> use or create a migrations history table (<code>__EFMigrationsHistory</code>).</li>



<li>Does <strong>not</strong> support incremental schema changes — there&#8217;s no concept of &#8220;diffing&#8221; old vs new model versions.</li>



<li>If you change your model (add a property, new entity, etc.) after the database was created, <code>EnsureCreated()</code> <strong>will not</strong> update the existing database. You&#8217;d have to drop and recreate it.</li>



<li>Cannot be mixed with Migrations on the same database — they use fundamentally different tracking mechanisms, and EF Core will throw an error if you try to apply migrations to a database created via <code>EnsureCreated()</code>.</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">When to use:</span></p>



<ul class="wp-block-list">
<li>Unit/integration tests (especially with in-memory or SQLite in-memory providers) where you just need a throwaway schema quickly.</li>



<li>Quick prototypes, demos, or POCs where schema evolution doesn&#8217;t matter.</li>



<li>Scenarios where you never intend to evolve the schema over time (rare in real apps).</li>
</ul>



<pre class="wp-block-code"><code>// Typical test setup
var options = new DbContextOptionsBuilder&lt;AppDbContext&gt;()
    .UseInMemoryDatabase("TestDb")
    .Options;

using var context = new AppDbContext(options);
context.Database.EnsureCreated(); // fast, one-time schema creation</code></pre>



<h3>EF Core Migrations</h3>



<pre class="wp-block-code"><code>dotnet ef migrations add InitialCreate
dotnet ef database update</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What it does:</span></p>



<ul class="wp-block-list">
<li>Generates a <strong>versioned migration file</strong> (C# code) representing the <em>diff</em> between your current model and the last known model snapshot.</li>



<li>Maintains a <code>__EFMigrationsHistory</code> table in the database that tracks which migrations have already been applied.</li>



<li>Allows <strong>incremental, repeatable, and reversible</strong> schema changes over the lifetime of an application.</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Key characteristics:</span></p>



<ul class="wp-block-list">
<li>Every schema change (add column, rename table, add index, etc.) is captured in its own migration file with <code>Up()</code> and <code>Down()</code> methods — enabling rollback.</li>



<li>Can be applied programmatically (<code>context.Database.Migrate()</code>) or via CLI/scripts — suitable for CI/CD pipelines.</li>



<li>Can generate raw SQL scripts (<code>dotnet ef migrations script</code>) for DBA review or production deployment without needing the EF tooling on the production server.</li>



<li>Supports team collaboration — migration files are checked into source control, so schema history is versioned alongside code.</li>
</ul>



<pre class="wp-block-code"><code>// Typical production startup
context.Database.Migrate(); // applies any pending migrations</code></pre>



<p><u>Side-by-side comparison:</u></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Aspect</th><th><code>EnsureCreated()</code></th><th>Migrations</th></tr></thead><tbody><tr><td>Schema versioning</td><td>None</td><td>Full history via migration files</td></tr><tr><td>Incremental changes</td><td>❌ Not supported</td><td>✅ Supported</td></tr><tr><td>Rollback capability</td><td>❌ No</td><td>✅ Yes (<code>Down()</code> method / previous migration)</td></tr><tr><td>Tracks applied changes in DB</td><td>❌ No history table</td><td>✅ <code>__EFMigrationsHistory</code> table</td></tr><tr><td>Suitable for production</td><td>❌ Generally no</td><td>✅ Yes</td></tr><tr><td>Suitable for tests/prototypes</td><td>✅ Yes</td><td>Possible but often overkill</td></tr><tr><td>Can coexist on same DB</td><td>❌ Mutually exclusive</td><td>❌ Mutually exclusive</td></tr><tr><td>CLI tooling required</td><td>No</td><td>Yes (<code>dotnet ef</code> / Package Manager Console)</td></tr></tbody></table></div>



<div class="noteBlock">Interview soundbite &#8211; 
EnsureCreated() is a one-shot, all-or-nothing schema creation useful for tests and prototypes — it has no concept of incremental change, so if my model evolves, it won&#8217;t update an existing database. Migrations, on the other hand, give me a versioned, reviewable, and reversible history of schema changes, tracked in an __EFMigrationsHistory table, which is what any real production application needs. The two are mutually exclusive — you pick one strategy per database.</div>
<p>Common follow-up: &#8220;What about EnsureDeleted()?&#8221;</p>
<p>Worth mentioning briefly — <code>context.Database.EnsureDeleted()</code> drops the database entirely if it exists. It&#8217;s often paired with <code>EnsureCreated()</code> in test setups to guarantee a clean slate before each test run:</p>



<pre class="wp-block-code"><code>context.Database.EnsureDeleted();
context.Database.EnsureCreated();</code></pre>



<p><b>53. What is the N+1 query problem ?</b></p>
<p>In EF Core, it happens when you fetch a set of entities, then access a navigation property on each one in a loop — and EF Core (with lazy loading enabled) fires off a separate query for each entity to resolve that navigation property.</p>
<p>Example:</p>



<pre class="wp-block-code"><code>var blogs = context.Blogs.ToList();  // 1 query — fetches all blogs

foreach (var blog in blogs)
{
    Console.WriteLine(blog.Posts.Count);  // N queries — one per blog to lazy-load Posts
}</code></pre>



<p>If there are 50 blogs, this fires 1 + 50 = 51 queries instead of one efficient query (or two well-batched ones).</p>
<p><u>Why it happens in EF Core specifically:</u></p>
<ul>
<li>Lazy loading is opt-in but easy to enable — via <span class="code">UseLazyLoadingProxies()</span> and making navigation properties <u>virtual</u>. Once enabled, simply touching a navigation property (<span class="code">blog.Posts</span>) triggers a new round-trip to the database, invisibly.</li>
<li>Even without lazy loading, developers sometimes manually call <span class="code">.Entry(blog).Collection(b => b.Posts).Load()</span> inside a loop — same effect, just explicit instead of hidden.</li>
</ul>
<p><b>How to fix it:</b></p>
<p>a. Eager loading with <code>Include() / ThenInclude()</code> — the most common EF Core fix:</p>



<pre class="wp-block-code"><code>var blogs = context.Blogs
    .Include(b =&gt; b.Posts)
    .ToList();  // 1 query total, using a SQL JOIN</code></pre>



<p>b. Explicit loading, but batched, not per-entity:</p>



<pre class="wp-block-code"><code>var blogIds = blogs.Select(b =&gt; b.Id).ToList();
var posts = context.Posts
    .Where(p =&gt; blogIds.Contains(p.BlogId))
    .ToList();  // 1 query for all related posts</code></pre>



<p>c. Projection with Select() — often the most efficient, since you only pull the columns you need:</p>



<pre class="wp-block-code"><code>var result = context.Blogs
    .Select(b =&gt; new {
        b.Name,
        PostCount = b.Posts.Count()
    })
    .ToList();  // 1 query, translated entirely to SQL</code></pre>



<p>d. Split queries (EF Core 5+) — when Include with multiple collections causes a cartesian explosion instead of N+1, you can tell EF to issue separate-but-batched queries instead of one giant join:</p>



<pre class="wp-block-code"><code>var blogs = context.Blogs
    .Include(b =&gt; b.Posts)
    .Include(b =&gt; b.Contributors)
    .AsSplitQuery()
    .ToList();</code></pre>



<p class="wp-block-paragraph">How to catch it:</p>



<ul class="wp-block-list">
<li>Enable <strong>EF Core logging</strong>: <code>optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information)</code> and watch for repeated similar-looking <code>SELECT</code> statements</li>



<li>Use <code>context.ChangeTracker.LazyLoadingEnabled = false</code> in development to make lazy-load accidents throw/fail loudly instead of silently querying</li>



<li>Tools like <strong>MiniProfiler</strong> or <strong>Application Insights</strong> can visually flag repeated queries in a request</li>



<li>As a rule of thumb: if lazy loading proxies are enabled, be very suspicious of any navigation-property access inside a <code>foreach</code></li>
</ul>



<p class="wp-block-paragraph"><strong>Key takeaway for EF Core:</strong> default to <code>Include()</code> for known access patterns, prefer <code>Select()</code> projections when you don&#8217;t need full entities, and treat lazy loading as something to use cautiously (or disable entirely) rather than as the default.</p>



<p><b>54. What is optimistic concurrency ?</b></p>
<p>It&#8217;s a strategy for handling simultaneous edits to the same data without locking rows — instead of preventing others from reading/editing a record while you have it open, EF Core lets everyone read and edit freely, but checks at save time whether the data changed since you loaded it. If it did, the update is rejected instead of silently overwriting someone else&#8217;s changes.</p>
<p>It&#8217;s called &#8220;optimistic&#8221; because it assumes conflicts are rare — so it doesn&#8217;t pay the cost of locking upfront, only checks when it actually matters (on save).</p>
<p><u>The problem it solves:</u></p>



<pre class="wp-block-code"><code>// User A loads a Blog (Name = "Tech News", RowVersion = 1)
// User B loads the same Blog (Name = "Tech News", RowVersion = 1)

// User A changes Name to "Tech Daily" and saves → RowVersion becomes 2
// User B changes Name to "Tech World" and saves...</code></pre>



<p>Without concurrency control, User B&#8217;s save would silently overwrite User A&#8217;s change — even though User B never saw it. Optimistic concurrency catches this instead of letting it happen silently.</p>
<p><u>How it works in EF Core:</u></p>
<p>You mark a property as a concurrency token. On every UPDATE/DELETE, EF Core includes that property&#8217;s original value in the WHERE clause. If no rows match (because someone else already changed it), EF Core throws a <code>DbUpdateConcurrencyException</code>.</p>
<p>a. Using a dedicated RowVersion / Timestamp column (most common, SQL Server):</p>



<pre class="wp-block-code"><code>public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    &#91;Timestamp]
    public byte&#91;] RowVersion { get; set; }  // auto-updated by the DB on every write
}</code></pre>



<p class="wp-block-paragraph">Generated SQL looks roughly like:</p>



<pre class="wp-block-code"><code>UPDATE Blogs 
SET Name = 'Tech World' 
WHERE Id = 1 AND RowVersion = 0x0000000000000001
-- if 0 rows affected → concurrency conflict</code></pre>



<p class="wp-block-paragraph">b.Using Fluent API on any property:</p>



<pre class="wp-block-code"><code>modelBuilder.Entity&lt;Blog&gt;()
    .Property(b =&gt; b.Name)
    .IsConcurrencyToken();</code></pre>



<p>Now any property you designate (not just a special RowVersion column) gets included in the WHERE clause — if the value in the DB differs from what was originally loaded, the update fails.</p>
<p>c. Handling the conflict:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
try
{
    context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
    foreach (var entry in ex.Entries)
    {
        var databaseValues = entry.GetDatabaseValues();
        
        if (databaseValues == null)
        {
            // Row was deleted by someone else
        }
        else
        {
            var currentValues = entry.CurrentValues;
            var originalValues = entry.OriginalValues;

            // Decide: overwrite with client&#039;s values, 
            // keep the database&#039;s values, 
            // or merge/prompt the user
            entry.OriginalValues.SetValues(databaseValues); // common: refresh original values, then retry
        }
    }

    context.SaveChanges(); // retry after resolving
}
</pre></div>


<h3 class="wp-block-heading">Optimistic vs. Pessimistic concurrency</h3>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th></th><th>Optimistic</th><th>Pessimistic</th></tr></thead><tbody><tr><td>Approach</td><td>Allow concurrent access, check at save time</td><td>Lock the row so others can&#8217;t touch it</td></tr><tr><td>Performance</td><td>Better — no locks held</td><td>Worse under contention — blocks other users</td></tr><tr><td>Best for</td><td>Web apps (disconnected, high concurrency, low actual conflict rate)</td><td>Short transactions where conflicts are frequent/expected</td></tr><tr><td>EF Core support</td><td>Built-in via concurrency tokens</td><td>Not natively supported — requires raw SQL / transaction-level locking hints</td></tr></tbody></table></div>



<p>EF Core supports optimistic concurrency natively and it&#8217;s the default recommended approach for typical web apps — pessimistic locking isn&#8217;t really a first-class EF Core feature and would need manual transaction/locking code. Use a <code>[Timestamp]</code> <code>RowVersion</code> column for the simplest, most reliable setup.</p>
<p><b>55. What is a transaction in EF Core?</b></p>



<p class="wp-block-paragraph">A transaction is a way to group multiple database operations together so they either <strong>all succeed or all fail as one unit</strong> — there&#8217;s no in-between state where only some of the changes got applied.</p>



<p class="wp-block-paragraph"><strong>The default behavior (implicit transaction)</strong>:</p>



<p class="wp-block-paragraph">You often don&#8217;t need to think about this because <strong><code>SaveChanges()</code> already wraps itself in a transaction automatically</strong>. If you modify multiple entities and call <code>SaveChanges()</code> once, EF Core executes all the resulting SQL statements inside a single transaction — if any statement fails, everything rolls back.</p>



<pre class="wp-block-code"><code>context.Blogs.Add(new Blog { Name = "Tech News" });
context.Posts.Add(new Post { Title = "Hello World", BlogId = 1 });

context.SaveChanges(); 
// Both inserts happen in ONE implicit transaction.
// If the second insert fails, the first is rolled back too.</code></pre>



<p class="wp-block-paragraph"><strong>When you need an explicit transaction:</strong></p>



<p class="wp-block-paragraph">The implicit one only covers a <strong>single <code>SaveChanges()</code> call</strong>. If you need to span <strong>multiple <code>SaveChanges()</code> calls</strong>, or mix EF Core with raw SQL, you need to manage the transaction explicitly.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using var transaction = context.Database.BeginTransaction();

try
{
    context.Blogs.Add(new Blog { Name = &quot;Tech News&quot; });
    context.SaveChanges(); // SaveChanges #1

    context.Posts.Add(new Post { Title = &quot;Hello World&quot;, BlogId = 1 });
    context.SaveChanges(); // SaveChanges #2

    transaction.Commit(); // only now are changes actually persisted
}
catch (Exception)
{
    transaction.Rollback(); // undo everything if anything failed
    throw;
}
</pre></div>


<p class="wp-block-paragraph">Or more concisely with <code>EnsureTransaction</code> / the built-in execution strategy wrapper:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using var transaction = context.Database.BeginTransaction();
try
{
    // multiple operations, possibly raw SQL too
    context.Database.ExecuteSqlRaw(&quot;UPDATE Blogs SET Name = &#039;X&#039; WHERE Id = 1&quot;);
    context.SaveChanges();
    transaction.Commit();
}
catch
{
    transaction.Rollback();
    throw;
}
</pre></div>


<p class="wp-block-paragraph"><strong>Transactions across multiple <code>DbContext</code> instances</strong>:</p>



<p class="wp-block-paragraph">If you need one transaction to span two separate <code>DbContext</code>s (e.g., two different bounded contexts), EF Core supports <strong>sharing a connection/transaction</strong>:</p>



<pre class="wp-block-code"><code>using var connection = new SqlConnection(connectionString);
connection.Open();
using var transaction = connection.BeginTransaction();

using (var context1 = new BlogContext(new DbContextOptionsBuilder&lt;BlogContext&gt;()
        .UseSqlServer(connection).Options))
{
    context1.Database.UseTransaction(transaction);
    // ... operations
}

using (var context2 = new OrderContext(new DbContextOptionsBuilder&lt;OrderContext&gt;()
        .UseSqlServer(connection).Options))
{
    context2.Database.UseTransaction(transaction);
    // ... operations
}

transaction.Commit();</code></pre>



<p class="wp-block-paragraph"><strong>Working with retry-resilient providers (e.g., <code>EnableRetryOnFailure</code>)</strong>:</p>



<p class="wp-block-paragraph">If you&#8217;ve enabled an execution strategy (like automatic retries for transient SQL Server failures), you can&#8217;t just call <code>BeginTransaction()</code> directly — the whole block, including retries, needs to be wrapped:</p>



<pre class="wp-block-code"><code>var strategy = context.Database.CreateExecutionStrategy();

strategy.Execute(() =&gt;
{
    using var transaction = context.Database.BeginTransaction();
    
    context.Blogs.Add(new Blog { Name = "Tech News" });
    context.SaveChanges();

    context.Posts.Add(new Post { Title = "Hello World" });
    context.SaveChanges();

    transaction.Commit();
});</code></pre>



<p class="wp-block-paragraph">This is necessary because if a transient failure happens mid-transaction and a naive retry just re-runs the code, you could end up trying to begin a transaction inside an already-failed one — <code>CreateExecutionStrategy()</code> handles this correctly.</p>



<p class="wp-block-paragraph"><strong>Savepoints (EF Core 5+):</strong></p>



<p class="wp-block-paragraph">Within a transaction, you can mark savepoints to roll back to a partial point without discarding the whole transaction:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using var transaction = context.Database.BeginTransaction();

context.Blogs.Add(new Blog { Name = &quot;A&quot; });
context.SaveChanges();
transaction.CreateSavepoint(&quot;AfterFirstBlog&quot;);

context.Blogs.Add(new Blog { Name = &quot;B&quot; });
context.SaveChanges();

// something went wrong with &quot;B&quot; specifically
transaction.RollbackToSavepoint(&quot;AfterFirstBlog&quot;); // &quot;A&quot; is kept, &quot;B&quot; is undone

transaction.Commit();
</pre></div>


<p class="wp-block-paragraph"><strong>Key takeaway</strong>:</p>



<ul class="wp-block-list">
<li><strong>Single <code>SaveChanges()</code> call</strong> → transaction is automatic, no code needed.</li>



<li><strong>Multiple <code>SaveChanges()</code> calls, or mixing with raw SQL</strong> → use <code>BeginTransaction()</code> / <code>Commit()</code> / <code>Rollback()</code> explicitly.</li>



<li><strong>Using <code>EnableRetryOnFailure</code></strong> → wrap explicit transactions in <code>CreateExecutionStrategy().Execute(...)</code>.</li>
</ul>



<h2>Authentication &#038; Authorization Interview Questions</h2>



<p>Authentication and Authorization are two of the most heavily tested topics in ASP.NET Core interviews. Authentication answers &#8220;who are you?&#8221; — verifying identity via credentials, tokens, or external providers — while Authorization answers &#8220;what are you allowed to do?&#8221;, determining access after identity is confirmed. Interviewers typically go beyond definitions to probe the mechanics: middleware pipeline order (UseAuthentication() before UseAuthorization()), JWT vs. cookie-based auth, ASP.NET Core Identity, and role-based vs. policy-based vs. claims-based authorization. Scenario questions are common too — like restricting an endpoint by a specific claim/role combo, or debugging why a valid user gets a 403 — testing whether you can apply the concepts, not just recite them.</p>
<p>These topics carry weight because security mistakes are among the costliest a developer can make — a broken authorization check can mean data breaches or full system compromise, unlike a minor UI bug. Interviewers use Auth questions as a proxy for engineering maturity and &#8220;secure by default&#8221; thinking, so candidates who explain the full lifecycle clearly — login, token issuance, claims validation, fine-grained authorization — tend to stand out, while textbook-only answers invite deeper follow-ups that expose gaps. For mid-level and senior roles especially, this is often a make-or-break area, so it&#8217;s worth mastering rather than just memorizing.</p>
<p><b>56. What is the difference between authentication and authorization ?</b></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th></th><th>Authentication</th><th>Authorization</th></tr></thead><tbody><tr><td>Question answered</td><td>&#8220;Who are you?&#8221;</td><td>&#8220;What are you allowed to do?&#8221;</td></tr><tr><td>Purpose</td><td>Verifies identity</td><td>Grants/denies access to resources</td></tr><tr><td>Happens</td><td>First</td><td>After authentication</td></tr><tr><td>Middleware</td><td><code>UseAuthentication()</code></td><td><code>UseAuthorization()</code></td></tr><tr><td>HTTP status on failure</td><td><code>401 Unauthorized</code></td><td><code>403 Forbidden</code></td></tr><tr><td>Based on</td><td>Credentials, tokens, cookies</td><td>Roles, claims, policies</td></tr></tbody></table></div>



<h3 class="wp-block-heading">Authentication</h3>



<p class="wp-block-paragraph">The process of verifying <strong>who the user is</strong> — checking credentials (username/password), a token (JWT), a cookie, or an external identity provider (Google, Microsoft, etc.), and establishing a <code>ClaimsPrincipal</code> representing the user.</p>



<pre class="wp-block-code"><code>// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =&gt;
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            // ...
        };
    });

app.UseAuthentication(); // must come BEFORE UseAuthorization</code></pre>



<p class="wp-block-paragraph">If authentication fails (bad/missing credentials), the server responds with <strong>401 Unauthorized</strong> — &#8220;I don&#8217;t know who you are.&#8221;</p>



<h3 class="wp-block-heading">Authorization</h3>



<p class="wp-block-paragraph">Once the user&#8217;s identity is established, authorization decides <strong>what that user is permitted to do</strong> — based on roles, claims, or custom policies.</p>



<pre class="wp-block-code"><code>&#91;Authorize(Roles = "Admin")]
&#91;HttpDelete("{id}")]
public IActionResult DeleteUser(int id) { ... }

// Policy-based
builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy("MinimumAge", policy =&gt;
        policy.RequireAssertion(context =&gt;
            context.User.HasClaim(c =&gt; c.Type == "Age") &amp;&amp;
            int.Parse(context.User.FindFirst("Age").Value) &gt;= 18));
});

app.UseAuthorization(); // must come AFTER UseAuthentication</code></pre>



<p class="wp-block-paragraph">If the user is authenticated but lacks the required role/claim/policy, the server responds with <strong>403 Forbidden</strong> — &#8220;I know who you are, but you&#8217;re not allowed to do this.&#8221;</p>



<p class="wp-block-paragraph"><strong>Key relationship</strong></p>



<p class="wp-block-paragraph">Authentication always happens <strong>before</strong> Authorization in the pipeline — you can&#8217;t check <em>what</em> someone can do until you know <em>who</em> they are. That&#8217;s also why middleware order matters:</p>



<pre class="wp-block-code"><code>app.UseAuthentication(); // 1. Establish identity
app.UseAuthorization();  // 2. Check permissions</code></pre>



<p class="wp-block-paragraph">Getting this order wrong (or reversing it) is a classic interview trick question, since misconfigured middleware order can silently allow unauthorized access or unexpectedly block valid users — a good example of why this distinction matters more in practice than it might seem in theory.</p>



<p><b>57. What is a Claims and Policies in ASP.NET Core authorization</b></p>



<p class="wp-block-paragraph">A <strong>Claim</strong> is a key-value pair that represents information about a user, issued by a trusted party (typically during authentication). It&#8217;s part of the <code>ClaimsIdentity</code>/<code>ClaimsPrincipal</code> model.</p>



<pre class="wp-block-code"><code>// Examples of claims
new Claim(ClaimTypes.Name, "John Doe")
new Claim(ClaimTypes.Email, "john@example.com")
new Claim("EmployeeId", "12345")
new Claim("Department", "Engineering")</code></pre>



<p class="wp-block-paragraph">Each claim has:</p>



<ul class="wp-block-list">
<li><strong>Type</strong> – what the claim represents (e.g., <code>"Department"</code>)</li>



<li><strong>Value</strong> – the actual data (e.g., <code>"Engineering"</code>)</li>



<li><strong>Issuer</strong> (optional) – who issued the claim</li>
</ul>



<p class="wp-block-paragraph">Claims are typically added during authentication (login) and bundled into a <code>ClaimsIdentity</code>, which becomes part of the <code>ClaimsPrincipal</code> (<code>User</code> object) accessible throughout the request.</p>



<pre class="wp-block-code"><code>var claims = new List&lt;Claim&gt;
{
    new Claim(ClaimTypes.Name, "jdoe"),
    new Claim("Department", "Engineering")
};
var identity = new ClaimsIdentity(claims, "MyAuthScheme");
var principal = new ClaimsPrincipal(identity);</code></pre>



<p class="wp-block-paragraph">You can check claims directly in code:</p>



<pre class="wp-block-code"><code>if (User.HasClaim("Department", "Engineering"))
{
    // allow access
}</code></pre>



<h3 class="wp-block-heading">Policy</h3>



<p class="wp-block-paragraph">A <strong>Policy</strong> is a named, reusable set of authorization requirements — often built using one or more claims — registered centrally so it can be applied via attributes instead of writing manual checks everywhere.</p>



<p class="wp-block-paragraph"><strong>a. Registering a policy</strong> (in <code>Program.cs</code>):</p>



<pre class="wp-block-code"><code>builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy("EngineeringOnly", policy =&gt;
        policy.RequireClaim("Department", "Engineering"));

    options.AddPolicy("MinimumAge", policy =&gt;
        policy.Requirements.Add(new MinimumAgeRequirement(18)));
});</code></pre>



<p class="wp-block-paragraph"><strong>b. Applying it</strong> to a controller or endpoint:</p>



<pre class="wp-block-code"><code>&#91;Authorize(Policy = "EngineeringOnly")]
public IActionResult SecretPage()
{
    return View();
}</code></pre>



<p class="wp-block-paragraph">Policies can be simple (<code>RequireClaim</code>, <code>RequireRole</code>, <code>RequireAuthenticatedUser</code>) or complex, using a custom <code>IAuthorizationRequirement</code> and <code>AuthorizationHandler&lt;T&gt;</code> for logic beyond a simple claim check (e.g., &#8220;user must be over 18,&#8221; verified via a date-of-birth claim).</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">How they relate:</span></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Concept</th><th>Role</th></tr></thead><tbody><tr><td><strong>Claim</strong></td><td>Raw piece of user data (who they are / what they have)</td></tr><tr><td><strong>Requirement</strong></td><td>A rule about claims/data that must be satisfied</td></tr><tr><td><strong>Policy</strong></td><td>A named bundle of one or more requirements</td></tr><tr><td><strong><code>[Authorize(Policy = "...")]</code></strong></td><td>Enforces the policy on an endpoint</td></tr></tbody></table></div>



<p>In short: <b>claims describe the user</b>, and <b>policies define the rules</b> used to decide whether those claims are sufficient to authorize access.</p>
<p><b>58. What is role-based authorization ?</b></p>



<p class="wp-block-paragraph"><strong>Role-based authorization</strong> restricts access to resources based on the <strong>role(s)</strong> a user belongs to (e.g., <code>Admin</code>, <code>Manager</code>, <code>User</code>) rather than checking individual permissions or claims one by one. It&#8217;s one of the simplest and most common authorization models.</p>



<p class="wp-block-paragraph">How it works:</p>



<ul class="wp-block-list">
<li>A role is essentially just a special claim of type <code>ClaimTypes.Role</code>.</li>



<li>When a user is authenticated, their roles are added to the <code>ClaimsPrincipal</code>.</li>



<li>Authorization checks whether the current user belongs to a required role before granting access.</li>
</ul>



<pre class="wp-block-code"><code>var claims = new List&lt;Claim&gt;
{
    new Claim(ClaimTypes.Name, "jdoe"),
    new Claim(ClaimTypes.Role, "Admin")
};
var identity = new ClaimsIdentity(claims, "MyAuthScheme");
var principal = new ClaimsPrincipal(identity);</code></pre>



<h3 class="wp-block-heading">Applying role-based authorization</h3>



<p class="wp-block-paragraph"><strong>a. Using the <code>[Authorize]</code> attribute:</strong></p>



<pre class="wp-block-code"><code>&#91;Authorize(Roles = "Admin")]
public IActionResult AdminPanel()
{
    return View();
}</code></pre>



<p class="wp-block-paragraph"><strong>b. Multiple roles (OR logic)</strong> — user needs <em>any one</em> of the listed roles:</p>



<pre class="wp-block-code"><code>&#91;Authorize(Roles = "Admin,Manager")]
public IActionResult ManageUsers()
{
    return View();
}</code></pre>



<p class="wp-block-paragraph"><strong>c. Multiple <code>[Authorize]</code> attributes (AND logic)</strong> — user must satisfy <em>all</em> of them:</p>



<pre class="wp-block-code"><code>&#91;Authorize(Roles = "Admin")]
&#91;Authorize(Roles = "HR")]
public IActionResult SensitiveAction()
{
    return View();
}</code></pre>



<p class="wp-block-paragraph"><strong>d. Checking roles in code:</strong></p>



<pre class="wp-block-code"><code>if (User.IsInRole("Admin"))
{
    // perform admin-only logic
}</code></pre>



<p class="wp-block-paragraph"><strong>e. Role-based policies</strong> (useful when combining with other requirements):</p>



<pre class="wp-block-code"><code>builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy("AdminOnly", policy =&gt;
        policy.RequireRole("Admin"));
});

&#91;Authorize(Policy = "AdminOnly")]
public IActionResult SecretSettings()
{
    return View();
}</code></pre>



<p class="wp-block-paragraph">Key points:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Aspect</th><th>Detail</th></tr></thead><tbody><tr><td>Basis</td><td>Claim of type <code>Role</code></td></tr><tr><td>Attribute</td><td><code>[Authorize(Roles = "...")]</code></td></tr><tr><td>Multiple roles in one attribute</td><td>OR condition</td></tr><tr><td>Stacked <code>[Authorize]</code> attributes</td><td>AND condition</td></tr><tr><td>Code check</td><td><code>User.IsInRole("RoleName")</code></td></tr><tr><td>Storage</td><td>Roles can come from a database, Identity, JWT tokens, Windows groups, etc.</td></tr></tbody></table></div>



<h3 class="wp-block-heading">Role-based vs. Claim-based vs. Policy-based</h3>



<ul class="wp-block-list">
<li><strong>Role-based</strong> → coarse-grained, checks a single &#8220;Role&#8221; claim (good for simple hierarchies like Admin/User).</li>



<li><strong>Claim-based</strong> → checks any claim type/value (e.g., <code>Department = "Engineering"</code>), more flexible.</li>



<li><strong>Policy-based</strong> → the most flexible; can combine roles, claims, and custom logic into a single named rule — and is the recommended approach in ASP.NET Core going forward, since roles and claims can both be expressed as policies.</li>
</ul>



<p><b>59. What is policy-based authorization ?</b></p>



<p class="wp-block-paragraph"><strong>Policy-based authorization</strong> is the most flexible and recommended authorization model in ASP.NET Core. Instead of scattering role or claim checks throughout your code, you define <strong>named policies</strong> centrally — each policy is a set of one or more <strong>requirements</strong> that must be satisfied — and then apply that policy declaratively wherever needed.</p>



<p class="wp-block-paragraph">It&#8217;s the underlying model that role-based and claims-based authorization are actually built on top of.</p>



<p class="wp-block-paragraph">Core building blocks:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Component</th><th>Purpose</th></tr></thead><tbody><tr><td><strong>Requirement</strong></td><td>A statement of what must be true (implements <code>IAuthorizationRequirement</code>)</td></tr><tr><td><strong>Handler</strong></td><td>Contains the logic that evaluates whether a requirement is met (<code>AuthorizationHandler&lt;T&gt;</code>)</td></tr><tr><td><strong>Policy</strong></td><td>A named collection of one or more requirements</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><strong>a. Simple policies (built-in requirements)</strong></p>



<p class="wp-block-paragraph">You don&#8217;t always need custom requirements — simple policies can use built-in methods:</p>



<pre class="wp-block-code"><code>builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy("EngineeringOnly", policy =&gt;
        policy.RequireClaim("Department", "Engineering"));

    options.AddPolicy("AdminOnly", policy =&gt;
        policy.RequireRole("Admin"));

    options.AddPolicy("MustBeAuthenticated", policy =&gt;
        policy.RequireAuthenticatedUser());
});</code></pre>



<p class="wp-block-paragraph"><strong>b. Custom policies (custom requirement + handler)</strong></p>



<p class="wp-block-paragraph">For logic that goes beyond a single claim check — e.g., &#8220;user must be at least 18 years old.&#8221;</p>



<p class="wp-block-paragraph">Step 1 – Define the requirement:</p>



<pre class="wp-block-code"><code>public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }
    public MinimumAgeRequirement(int minimumAge) =&gt; MinimumAge = minimumAge;
}</code></pre>



<p class="wp-block-paragraph">Step 2 – Create the handler:</p>



<pre class="wp-block-code"><code>public class MinimumAgeHandler : AuthorizationHandler&lt;MinimumAgeRequirement&gt;
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dobClaim = context.User.FindFirst(c =&gt; c.Type == ClaimTypes.DateOfBirth);
        if (dobClaim == null)
            return Task.CompletedTask;

        var dob = DateTime.Parse(dobClaim.Value);
        var age = DateTime.Today.Year - dob.Year;

        if (age &gt;= requirement.MinimumAge)
            context.Succeed(requirement);

        return Task.CompletedTask;
    }
}</code></pre>



<p class="wp-block-paragraph">Step 3 – Register the handler and policy:</p>



<pre class="wp-block-code"><code>builder.Services.AddSingleton&lt;IAuthorizationHandler, MinimumAgeHandler&gt;();

builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy("MinimumAge18", policy =&gt;
        policy.Requirements.Add(new MinimumAgeRequirement(18)));
});</code></pre>



<p class="wp-block-paragraph">Step 4 – Apply it:</p>



<pre class="wp-block-code"><code>&#91;Authorize(Policy = "MinimumAge18")]
public IActionResult AdultContent()
{
    return View();
}</code></pre>



<p class="wp-block-paragraph"><strong>c. Combining multiple requirements</strong></p>



<p class="wp-block-paragraph">A policy can require several things at once — all requirements must succeed:</p>



<pre class="wp-block-code"><code>options.AddPolicy("SeniorEngineer", policy =&gt;
{
    policy.RequireRole("Engineer");
    policy.RequireClaim("Level", "Senior");
    policy.Requirements.Add(new MinimumAgeRequirement(21));
});</code></pre>



<p class="wp-block-paragraph"><strong>d. Imperative checks (outside attributes)</strong></p>



<p class="wp-block-paragraph">Using <code>IAuthorizationService</code> when you need to check a policy in code, e.g., inside a Razor Page or controller action:</p>



<pre class="wp-block-code"><code>public class DocumentController : Controller
{
    private readonly IAuthorizationService _authorizationService;

    public DocumentController(IAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
    }

    public async Task&lt;IActionResult&gt; Edit(Document doc)
    {
        var result = await _authorizationService.AuthorizeAsync(User, doc, "EditPolicy");
        if (!result.Succeeded)
            return Forbid();

        return View(doc);
    }
}</code></pre>



<p class="wp-block-paragraph">This example also shows <strong>resource-based authorization</strong> — passing the actual resource (<code>doc</code>) so the handler can check ownership or other resource-specific rules, not just claims on the user.</p>



<p class="wp-block-paragraph">Why policy-based is preferred:</p>



<ul class="wp-block-list">
<li><strong>Centralized</strong> – all authorization rules live in one place (<code>Program.cs</code> / a startup extension), not scattered across controllers.</li>



<li><strong>Reusable</strong> – one policy can be applied to many endpoints.</li>



<li><strong>Composable</strong> – combine roles, claims, and custom logic in a single policy.</li>



<li><strong>Testable</strong> – handlers are plain classes that can be unit tested independently.</li>



<li><strong>Extensible</strong> – supports resource-based authorization for per-object rules (e.g., &#8220;only the document owner can edit&#8221;).</li>
</ul>



<p class="wp-block-paragraph">Quick comparison:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Model</th><th>Granularity</th><th>Flexibility</th><th>Typical Use</th></tr></thead><tbody><tr><td>Role-based</td><td>Coarse</td><td>Low</td><td>Simple hierarchies (Admin/User)</td></tr><tr><td>Claims-based</td><td>Medium</td><td>Medium</td><td>Attribute checks (Department, Age)</td></tr><tr><td>Policy-based</td><td>Fine</td><td>High</td><td>Complex, reusable, combined rules — recommended default</td></tr></tbody></table></div>



<p><b>60. What is claims-based authorization ?</b></p>



<p class="wp-block-paragraph"><strong>Claims-based authorization</strong> restricts access based on the presence (and optionally the value) of specific <strong>claims</strong> in the user&#8217;s <code>ClaimsPrincipal</code> — rather than relying purely on roles. A claim is a key-value pair issued by a trusted party (e.g., <code>"Department" = "Engineering"</code>, <code>"EmployeeId" = "12345"</code>), so this model lets you authorize based on <em>any</em> attribute of the user, not just a fixed role name.</p>



<p class="wp-block-paragraph">It sits conceptually between role-based (single, coarse <code>Role</code> claim) and full policy-based authorization (which claims-based checks are actually built on top of).</p>



<p class="wp-block-paragraph">How claims get into the user:</p>



<p class="wp-block-paragraph">Claims are added during authentication and bundled into a <code>ClaimsIdentity</code> → <code>ClaimsPrincipal</code>:</p>



<pre class="wp-block-code"><code>var claims = new List&lt;Claim&gt;
{
    new Claim(ClaimTypes.Name, "jdoe"),
    new Claim("Department", "Engineering"),
    new Claim("EmployeeId", "12345"),
    new Claim(ClaimTypes.DateOfBirth, "2000-05-10")
};
var identity = new ClaimsIdentity(claims, "MyAuthScheme");
var principal = new ClaimsPrincipal(identity);</code></pre>



<h3 class="wp-block-heading">Applying claims-based authorization</h3>



<p class="wp-block-paragraph">Unlike roles, there&#8217;s no <code>[Authorize(Claim = "...")]</code> shortcut attribute — claims checks are expressed through <strong>policies</strong>.</p>



<p class="wp-block-paragraph"><strong>a. Define a policy that requires a claim:</strong></p>



<pre class="wp-block-code"><code>builder.Services.AddAuthorization(options =&gt;
{
    // Just checks the claim type exists (any value)
    options.AddPolicy("HasEmployeeId", policy =&gt;
        policy.RequireClaim("EmployeeId"));

    // Checks claim type AND specific value(s)
    options.AddPolicy("EngineeringOnly", policy =&gt;
        policy.RequireClaim("Department", "Engineering"));

    // Multiple allowed values (OR)
    options.AddPolicy("EngineeringOrIT", policy =&gt;
        policy.RequireClaim("Department", "Engineering", "IT"));
});</code></pre>



<p class="wp-block-paragraph"><strong>b. Apply it to a controller/endpoint:</strong></p>



<pre class="wp-block-code"><code>&#91;Authorize(Policy = "EngineeringOnly")]
public IActionResult TeamDashboard()
{
    return View();
}</code></pre>



<p class="wp-block-paragraph"><strong>c. Check claims directly in code:</strong></p>



<pre class="wp-block-code"><code>if (User.HasClaim("Department", "Engineering"))
{
    // allow access
}

var employeeId = User.FindFirst("EmployeeId")?.Value;</code></pre>



<p class="wp-block-paragraph"><strong>d. Custom claims logic (beyond a simple match)</strong> — use a custom requirement/handler when you need to evaluate a claim&#8217;s value with logic (e.g., parsing a date, comparing numbers):</p>



<pre class="wp-block-code"><code>public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }
    public MinimumAgeRequirement(int age) =&gt; MinimumAge = age;
}

public class MinimumAgeHandler : AuthorizationHandler&lt;MinimumAgeRequirement&gt;
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dobClaim = context.User.FindFirst(ClaimTypes.DateOfBirth);
        if (dobClaim != null)
        {
            var age = DateTime.Today.Year - DateTime.Parse(dobClaim.Value).Year;
            if (age &gt;= requirement.MinimumAge)
                context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}</code></pre>



<p class="wp-block-paragraph">Key points:</p>



<ul class="wp-block-list">
<li>A <strong>role is just a special-cased claim</strong> (<code>ClaimTypes.Role</code>), so role-based authorization is really a narrow form of claims-based authorization.</li>



<li>Claims-based checks are always wired through <strong><code>AddPolicy</code> + <code>RequireClaim</code></strong> (or a custom handler) — there&#8217;s no built-in <code>[Authorize(Claim=...)]</code> attribute.</li>



<li>Best suited when authorization depends on <strong>user attributes</strong> beyond a simple role (department, subscription tier, verified email, age, permission flags, etc.).</li>



<li>Can be combined with roles and custom requirements in the same policy for fine-grained control.</li>
</ul>



<p><b>61. What is permission-based authorization ?</b></p>



<p class="wp-block-paragraph"><strong>Permission-based authorization</strong> restricts access based on <strong>fine-grained permissions</strong> (e.g., <code>"Users.Create"</code>, <code>"Orders.Delete"</code>, <code>"Reports.View"</code>) assigned to a user — rather than broad roles like <code>Admin</code> or <code>Manager</code>. It&#8217;s the most granular authorization model: instead of asking &#8220;what role is this user?&#8221;, it asks &#8220;can this user perform <em>this specific action</em>?&#8221;</p>



<p class="wp-block-paragraph">ASP.NET Core has <strong>no built-in permission system</strong> — it&#8217;s typically implemented on top of <strong>claims-based</strong> and <strong>policy-based</strong> authorization.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why permission-based over role-based?</span></p>



<p class="wp-block-paragraph">Roles become a problem at scale:</p>



<ul class="wp-block-list">
<li>A <code>Manager</code> role might need 30 different permissions, and hardcoding <code>[Authorize(Roles = "Manager")]</code> everywhere makes it hard to give one manager extra access without creating a new role.</li>



<li>Permissions decouple <strong>&#8220;what a user can do&#8221;</strong> from <strong>&#8220;what a user is called&#8221;</strong> — roles become just a convenient <em>bundle</em> of permissions, not the authorization unit itself.</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Common implementation approach</span></p>



<p class="wp-block-paragraph"><strong>a. Model permissions as claims</strong></p>



<pre class="wp-block-code"><code>var claims = new List&lt;Claim&gt;
{
    new Claim(ClaimTypes.Name, "jdoe"),
    new Claim("Permission", "Orders.View"),
    new Claim("Permission", "Orders.Edit"),
    new Claim("Permission", "Reports.View")
};
var identity = new ClaimsIdentity(claims, "MyAuthScheme");</code></pre>



<p class="wp-block-paragraph">Permissions are usually stored in a database (<code>Roles → Permissions</code> mapping table), and converted into claims when the user logs in / token is issued.</p>



<p class="wp-block-paragraph"><strong>b. Define a custom requirement</strong></p>



<pre class="wp-block-code"><code>public class PermissionRequirement : IAuthorizationRequirement
{
    public string Permission { get; }
    public PermissionRequirement(string permission) =&gt; Permission = permission;
}</code></pre>



<p class="wp-block-paragraph"><strong>c. Create the handler</strong></p>



<pre class="wp-block-code"><code>public class PermissionHandler : AuthorizationHandler&lt;PermissionRequirement&gt;
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        if (context.User.HasClaim("Permission", requirement.Permission))
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}</code></pre>



<p class="wp-block-paragraph"><strong>d. Register a policy per permission — or generate them dynamically</strong></p>



<p class="wp-block-paragraph">Manually:</p>



<pre class="wp-block-code"><code>builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy("Orders.Edit", policy =&gt;
        policy.Requirements.Add(new PermissionRequirement("Orders.Edit")));
});</code></pre>



<p class="wp-block-paragraph">Dynamically (better for many permissions), using an <code>IAuthorizationPolicyProvider</code>:</p>



<pre class="wp-block-code"><code>public class PermissionPolicyProvider : IAuthorizationPolicyProvider
{
    public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }

    public PermissionPolicyProvider(IOptions&lt;AuthorizationOptions&gt; options)
    {
        FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
    }

    public Task&lt;AuthorizationPolicy&gt; GetPolicyAsync(string policyName)
    {
        var policy = new AuthorizationPolicyBuilder()
            .AddRequirements(new PermissionRequirement(policyName))
            .Build();
        return Task.FromResult(policy);
    }

    public Task&lt;AuthorizationPolicy&gt; GetDefaultPolicyAsync() =&gt;
        FallbackPolicyProvider.GetDefaultPolicyAsync();

    public Task&lt;AuthorizationPolicy?&gt; GetFallbackPolicyAsync() =&gt;
        FallbackPolicyProvider.GetFallbackPolicyAsync();
}</code></pre>



<pre class="wp-block-code"><code>builder.Services.AddSingleton&lt;IAuthorizationPolicyProvider, PermissionPolicyProvider&gt;();
builder.Services.AddSingleton&lt;IAuthorizationHandler, PermissionHandler&gt;();</code></pre>



<p class="wp-block-paragraph">This lets you use <strong>any permission string directly as a policy name</strong> without pre-registering each one:</p>



<pre class="wp-block-code"><code>&#91;Authorize(Policy = "Orders.Edit")]
public IActionResult EditOrder(int id) { ... }

&#91;Authorize(Policy = "Reports.View")]
public IActionResult ViewReports() { ... }</code></pre>



<p class="wp-block-paragraph"><strong>e. Check permissions imperatively</strong></p>



<pre class="wp-block-code"><code>var result = await _authorizationService.AuthorizeAsync(User, "Orders.Delete");
if (!result.Succeeded)
    return Forbid();</code></pre>



<p class="wp-block-paragraph">Or directly via claims:</p>



<pre class="wp-block-code"><code>if (User.HasClaim("Permission", "Orders.Delete"))
{
    // allow
}</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Typical database design:</span></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Table</th><th>Purpose</th></tr></thead><tbody><tr><td><code>Users</code></td><td>User accounts</td></tr><tr><td><code>Roles</code></td><td>Named role groupings</td></tr><tr><td><code>Permissions</code></td><td>Fine-grained actions (e.g., <code>Orders.Edit</code>)</td></tr><tr><td><code>RolePermissions</code></td><td>Maps roles → permissions</td></tr><tr><td><code>UserRoles</code></td><td>Maps users → roles</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">At login, the app resolves the user&#8217;s roles → collects all associated permissions → issues them as <code>Permission</code> claims (or embeds them in a JWT).</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Comparison across all four models</span>:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Model</th><th>Granularity</th><th>Basis</th><th>Typical check</th></tr></thead><tbody><tr><td><strong>Role-based</strong></td><td>Coarse</td><td><code>Role</code> claim</td><td><code>[Authorize(Roles = "Admin")]</code></td></tr><tr><td><strong>Claims-based</strong></td><td>Medium</td><td>Any claim</td><td><code>RequireClaim("Department", "Engineering")</code></td></tr><tr><td><strong>Policy-based</strong></td><td>Fine</td><td>Requirements/handlers</td><td><code>[Authorize(Policy = "MinimumAge18")]</code></td></tr><tr><td><strong>Permission-based</strong></td><td>Finest</td><td><code>Permission</code> claims + custom handler</td><td><code>[Authorize(Policy = "Orders.Edit")]</code></td></tr></tbody></table></div>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Key points:</span></p>



<ul class="wp-block-list">
<li>Not a distinct ASP.NET Core feature — it&#8217;s <strong>claims + policies applied at action-level granularity</strong>.</li>



<li>Best for systems needing <strong>fine-grained access control</strong> (e.g., admin dashboards, multi-tenant SaaS apps) where roles alone would multiply out of control.</li>



<li>Often combined with a dynamic <code>IAuthorizationPolicyProvider</code> so you don&#8217;t have to manually register hundreds of policies.</li>



<li>Roles can still exist as a <strong>UI/management convenience</strong> (grouping permissions), while actual authorization decisions are made on permissions.</li>
</ul>



<p><b>62. How does JWT authentication work and explain it&#8217;s three parts ?</b></p>



<p class="wp-block-paragraph"><strong>JWT authentication</strong> is a stateless authentication mechanism where, after a user logs in with valid credentials, the server generates a signed JSON Web Token (JWT) containing the user&#8217;s identity and claims, and sends it back to the client. The client then includes this token in the <code>Authorization: Bearer &lt;token&gt;</code> header on every subsequent request, and the server simply verifies the token&#8217;s signature and expiration to authenticate the user — without needing to query a database or maintain session state. Since the token itself carries all the necessary user information and is cryptographically signed (though not encrypted) to prevent tampering, JWT authentication scales well for distributed systems and APIs, such as those built with ASP.NET Core, where multiple servers can validate tokens independently without shared session storage.</p>



<p class="wp-block-paragraph"><strong>How it works:</strong></p>



<ol class="wp-block-list">
<li>User logs in with credentials (username/password).</li>



<li>Server validates credentials and generates a <strong>JWT (JSON Web Token)</strong>, signed with a secret/private key.</li>



<li>Server sends the token back to the client.</li>



<li>Client stores the token (e.g., in memory, localStorage) and sends it in the <code>Authorization: Bearer &lt;token&gt;</code> header on every subsequent request.</li>



<li>Server verifies the token&#8217;s signature and expiry on each request — no database/session lookup needed — and extracts claims to identify/authorize the user.</li>
</ol>



<p class="wp-block-paragraph"><strong>Three parts</strong> (separated by dots: <code>header.payload.signature</code>):</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead>
<tr class="table-primary"><th>Part</th><th>Content</th></tr></thead><tbody><tr><td><strong>Header</strong></td><td>Metadata — token type (<code>JWT</code>) and signing algorithm (e.g., <code>HS256</code>)</td></tr><tr><td><strong>Payload</strong></td><td>Claims — user data like <code>sub</code> (user ID), <code>role</code>, <code>exp</code> (expiry), custom claims</td></tr><tr><td><strong>Signature</strong></td><td>Header + payload, hashed with a secret key — used to verify the token hasn&#8217;t been tampered with</td></tr></tbody></table></div>



<p class="wp-block-paragraph">Example: <code>eyJhbGciOi...</code> (header) <code>.eyJzdWIiOi...</code> (payload) <code>.SflKxwRJ...</code> (signature)</p>



<p>Since the signature guarantees integrity (not secrecy — payload is just Base64-encoded, not encrypted), JWTs enable <strong>stateless authentication</strong>.</p>
<div class="starBlock"><p>You can also visit my series on <u>JSON Web Token (JWT)</u> in ASP.NET Core. There are 3 tutorials to master JWT:</p>
<ol>
<li><a target="_blank" href="https://www.yogihosting.com/jwt-api-aspnet-core/" rel="noopener noreferrer">ASP.NET Core JWT Authentication and Authorization of Web API [Detailed]</a></li>
<li><a target="_blank" href="https://www.yogihosting.com/jwt-refresh-token-aspnet-core/" rel="noopener">Implementing JWT Refresh Token in ASP.NET Core MVC</a></li>
<li><a target="_blank" href="https://www.yogihosting.com/jwt-jquery-aspnet-core/" rel="noopener">How to call a JWT secured APIs with jQuery AJAX [with source codes]</a></li>
</ol>
</div>



<p><b>63. Why must UseAuthentication() come before UseAuthorization() ?</b></p>



<p class="wp-block-paragraph">Because <strong>authorization depends on the result of authentication</strong>. Middleware runs in the order it&#8217;s registered, and:</p>



<ul class="wp-block-list">
<li><code>UseAuthentication()</code> identifies <strong>who</strong> the user is — it reads the request (e.g., cookie, JWT token), validates it, and populates <code>HttpContext.User</code> with a <code>ClaimsPrincipal</code>.</li>



<li><code>UseAuthorization()</code> decides <strong>what</strong> that user is allowed to do — it checks roles/claims/policies against <code>HttpContext.User</code>.</li>
</ul>



<p class="wp-block-paragraph">If <code>UseAuthorization()</code> ran first, <code>HttpContext.User</code> would still be an empty/unauthenticated principal (no claims set), so every authorization check would fail — even for legitimately authenticated users — because there&#8217;d be no identity to evaluate yet.</p>



<pre class="wp-block-code"><code>app.UseAuthentication(); // Sets HttpContext.User
app.UseAuthorization();  // Checks HttpContext.User against policies/roles</code></pre>



<p class="wp-block-paragraph"><strong>In short:</strong> Authorization needs a populated <code>User</code> to check against — and only authentication middleware populates it. Wrong order → authorization always fails.</p>



<p><b>64. What is OpenID Connect ?</b></p>



<p class="wp-block-paragraph"><strong>OpenID Connect</strong> is an authentication protocol built on top of <strong>OAuth 2.0</strong>. While OAuth 2.0 is designed for <em>authorization</em> (granting access to resources), OIDC adds an <em>identity layer</em> on top of it, allowing clients to verify the identity of a user and obtain basic profile information.</p>



<p class="wp-block-paragraph">Key Points:</p>



<ul class="wp-block-list">
<li><strong>Authentication, not just authorization</strong>: OAuth 2.0 tells you &#8220;this app has permission to access X,&#8221; but it doesn&#8217;t tell you <em>who</em> the user is. OIDC solves this by introducing the concept of an <strong>ID Token</strong>.</li>



<li><strong>ID Token</strong>: A JSON Web Token (JWT) issued by the identity provider (IdP) after successful authentication. It contains claims about the user (e.g., <code>sub</code>, <code>name</code>, <code>email</code>, <code>iat</code>, <code>exp</code>) that the client application can verify and trust.</li>



<li><strong>Standardized flow</strong>: Uses the same flows as OAuth 2.0 (Authorization Code, Implicit, etc.), but the token response includes both an <strong>access token</strong> (for authorization) and an <strong>ID token</strong> (for authentication).</li>
</ul>



<p class="wp-block-paragraph">How it Works (Simplified Flow):</p>



<ol class="wp-block-list">
<li>The client redirects the user to the Identity Provider (e.g., IdentityServer, Azure AD, Google).</li>



<li>The user authenticates (logs in) with the IdP.</li>



<li>The IdP redirects back to the client with an <strong>authorization code</strong>.</li>



<li>The client exchanges the code for an <strong>access token</strong> and an <strong>ID token</strong>.</li>



<li>The client validates the ID token&#8217;s signature, issuer, audience, and expiry.</li>



<li>The client extracts user claims from the ID token to establish the user&#8217;s identity.</li>
</ol>



<p class="wp-block-paragraph">Example in ASP.NET Core:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
builder.Services.AddAuthentication(options =&gt;
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = &quot;oidc&quot;;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(&quot;oidc&quot;, options =&gt;
{
    options.Authority = &quot;https://your-identity-provider.com&quot;;
    options.ClientId = &quot;your-client-id&quot;;
    options.ClientSecret = &quot;your-client-secret&quot;;
    options.ResponseType = &quot;code&quot;;
    options.SaveTokens = true;
    options.Scope.Add(&quot;openid&quot;);
    options.Scope.Add(&quot;profile&quot;);
});
</pre></div>


<p><b>65. What is OAuth 2.0 ? What is the difference between OAuth 2.0 and OpenID Connect ?</b></p>



<p class="wp-block-paragraph"><strong>OAuth 2.0</strong> is an <strong>authorization</strong> framework (protocol) that allows a third-party application to obtain limited access to a user&#8217;s resources on another service, <strong>without exposing the user&#8217;s credentials</strong> (like username/password) to that third-party application.</p>



<p class="wp-block-paragraph"><strong>Key Idea</strong>:</p>



<p class="wp-block-paragraph">Instead of giving your password to an app, you grant it a <strong>token</strong> that allows limited, specific access — for a limited time — to your data hosted on another service.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">Example: You want a photo-printing app to access your Google Photos. Instead of giving the app your Google password, Google issues an <strong>access token</strong> that only allows access to your photos, nothing else (not your emails, contacts, etc.).</p>
</blockquote>



<p class="wp-block-paragraph"><strong>Key Roles in OAuth 2.0</strong>:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Role</th><th>Description</th></tr></thead><tbody><tr><td><strong>Resource Owner</strong></td><td>The user who owns the data (e.g., you)</td></tr><tr><td><strong>Client</strong></td><td>The application requesting access (e.g., the photo-printing app)</td></tr><tr><td><strong>Authorization Server</strong></td><td>Issues access tokens after authenticating the resource owner (e.g., Google&#8217;s auth server)</td></tr><tr><td><strong>Resource Server</strong></td><td>Hosts the protected resources/APIs (e.g., Google Photos API)</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><strong>Key Concepts</strong>:</p>



<ul class="wp-block-list">
<li><strong>Access Token</strong>: A credential (often a JWT or opaque string) used by the client to access protected resources. It has a limited lifetime and scope.</li>



<li><strong>Refresh Token</strong>: Used to obtain a new access token once the current one expires, without requiring the user to log in again.</li>



<li><strong>Scope</strong>: Defines the level/boundary of access requested (e.g., <code>read:photos</code>, <code>write:contacts</code>).</li>



<li><strong>Grant Type</strong>: The method by which the client obtains an access token.</li>
</ul>



<p class="wp-block-paragraph"><strong>Common OAuth 2.0 Grant Types (Flows)</strong>:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Grant Type</th><th>Use Case</th></tr></thead><tbody><tr><td><strong>Authorization Code</strong></td><td>Most secure; used by web apps with a backend (server-side)</td></tr><tr><td><strong>Authorization Code + PKCE</strong></td><td>Recommended for SPAs and mobile apps (no client secret)</td></tr><tr><td><strong>Client Credentials</strong></td><td>Machine-to-machine (M2M) communication, no user involved</td></tr><tr><td><strong>Implicit</strong> (deprecated)</td><td>Was used for SPAs, now replaced by Auth Code + PKCE</td></tr><tr><td><strong>Resource Owner Password Credentials</strong> (deprecated)</td><td>Client collects username/password directly — insecure, avoid</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><strong>OIDC vs OAuth 2.0</strong></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Aspect</th><th>OAuth 2.0</th><th>OpenID Connect</th></tr></thead><tbody><tr><td>Purpose</td><td>Authorization</td><td>Authentication (+ Authorization)</td></tr><tr><td>Token</td><td>Access Token</td><td>ID Token + Access Token</td></tr><tr><td>Tells you</td><td>What you can access</td><td>Who the user is</td></tr><tr><td>Token format</td><td>Opaque or JWT</td><td>JWT (ID Token is always a JWT)</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><strong>Common Claims in an ID Token</strong></p>



<ul class="wp-block-list">
<li><code>sub</code> — unique identifier for the user</li>



<li><code>iss</code> — issuer (the identity provider)</li>



<li><code>aud</code> — audience (the client app)</li>



<li><code>exp</code> — expiration time</li>



<li><code>iat</code> — issued at time</li>



<li><code>name</code>, <code>email</code>, <code>email_verified</code> — profile information (if requested via scopes)</li>
</ul>



<p class="wp-block-paragraph"><strong>In short</strong>: OIDC = OAuth 2.0 + Identity. It&#8217;s the standard way to implement &#8220;Login with Google/Microsoft/Facebook&#8221; style authentication in modern applications, including ASP.NET Core apps using <code>AddOpenIdConnect()</code> or integrating with IdentityServer/Duende IdentityServer.</p>



<h2>Duende IdentityServer</h2>
<p>Duende IdentityServer questions are important to prepare for in ASP.NET Core interviews because authentication and authorization are core concerns in almost every real-world enterprise application, and Duende IdentityServer is the de facto standard for implementing centralized identity management, SSO, and API security in the .NET ecosystem — so interviewers use it to gauge whether you truly understand OAuth 2.0 and OpenID Connect concepts in practice, not just in theory. Many companies have existing systems built on IdentityServer4 or are migrating to Duende IdentityServer, so employers want to confirm you can configure clients, scopes, resources, and grant types, handle token issuance and validation, integrate custom user stores, and troubleshoot common issues like token expiration, CORS, or redirect URI mismatches — all of which reflect real production challenges. Additionally, since security vulnerabilities in authentication systems can have severe consequences, interviewers use these questions to assess whether a candidate has the judgment and depth needed to design and maintain a secure, standards-compliant identity solution rather than just copy-pasting configuration code, making this topic a strong signal of a candidate&#8217;s practical, production-ready expertise versus surface-level knowledge.</p>
<div class="starBlock">I have written 4 complete tutorials on Duende IdentityServer. You can visit them from here:
<ol>
<li><a href="https://www.yogihosting.com/aspnet-core-identity-mongodb/">ASP.NET Core Identity with MongoDB as Database</a></li>
<li><a href="https://www.yogihosting.com/identityserver-aspnet-core-identity-mongodb-database/">Duende IdentityServer with ASP.NET Core Identity and MongoDB as Database</a></li>
<li><a href="https://www.yogihosting.com/identityserver-role-policy-authentication-aspnet-core/">Duende IdentityServer Role and Policy Based Authentication</a></li>
<li><a href="https://www.yogihosting.com/aspnet-core-duende-identityserver/">ASP.NET Core – Duende IdentityServer authentication and authorization with Identity</a></li>
</ol>
</div>
<p><b>66. What is Duende Software IdentityServer ?</b></p>



<p class="wp-block-paragraph"><strong>Duende IdentityServer</strong> is a commercial, open-source <strong>OpenID Connect and OAuth 2.0</strong> framework for <strong>.NET</strong>, used to build a centralized <strong>authentication and authorization server</strong> (also called a <strong>Security Token Service / STS</strong>) in ASP.NET Core applications. It is the officially supported successor to <strong>IdentityServer4</strong>, developed by Duende Software after the original creators discontinued free maintenance of IdentityServer4.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why It Exists:</span></p>



<p class="wp-block-paragraph">In enterprise applications, you often have <strong>multiple client applications</strong> (web apps, SPAs, mobile apps) and <strong>multiple APIs</strong> that all need to authenticate users and authorize access consistently. Instead of each app implementing its own login and token logic, Duende IdentityServer centralizes this into <strong>one identity provider</strong> that all apps trust.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What It Does:</span></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Capability</th><th>Description</th></tr></thead><tbody><tr><td><strong>Authentication</strong></td><td>Issues <strong>ID Tokens</strong> to verify user identity (OIDC)</td></tr><tr><td><strong>Authorization</strong></td><td>Issues <strong>Access Tokens</strong> to allow API access (OAuth 2.0)</td></tr><tr><td><strong>Single Sign-On (SSO)</strong></td><td>One login session works across multiple client apps</td></tr><tr><td><strong>Federation</strong></td><td>Supports external login providers (Google, Azure AD, etc.)</td></tr><tr><td><strong>Token Management</strong></td><td>Handles refresh tokens, token expiry, revocation</td></tr><tr><td><strong>Custom User Store</strong></td><td>Integrates with ASP.NET Core Identity, EF Core, or custom databases</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Key Building Blocks (Configuration Concepts):</span></p>



<ul class="wp-block-list">
<li><strong>Clients</strong> — the applications allowed to request tokens (e.g., a web app, a mobile app)</li>



<li><strong>Scopes</strong> — what the client is allowed to request (<code>openid</code>, <code>profile</code>, <code>api1.read</code>)</li>



<li><strong>Resources</strong> — APIs or identity data being protected</li>



<li><strong>Grant Types</strong> — flow used to get a token (Authorization Code + PKCE is the modern standard)</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Example: Minimal Setup in ASP.NET Core</span></p>



<pre class="wp-block-code"><code>builder.Services.AddIdentityServer()
    .AddInMemoryClients(Config.Clients)
    .AddInMemoryApiScopes(Config.ApiScopes)
    .AddInMemoryIdentityResources(Config.IdentityResources)
    .AddTestUsers(Config.Users); // or AddAspNetIdentity&lt;ApplicationUser&gt;()

app.UseIdentityServer();</code></pre>



<p class="wp-block-paragraph">A client app then consumes it like this:</p>



<pre class="wp-block-code"><code>.AddOpenIdConnect("oidc", options =&gt;
{
    options.Authority = "https://your-duende-identityserver.com";
    options.ClientId = "webapp-client";
    options.ResponseType = "code";
});</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why Interviewers Ask This</span></p>



<ol class="wp-block-list">
<li><strong>Tests real-world knowledge</strong> — most companies don&#8217;t build raw OAuth/OIDC flows manually; they use a framework like this, so interviewers want to know if you&#8217;ve actually implemented it.</li>



<li><strong>Checks migration awareness</strong> — since IdentityServer4 is deprecated (EOL Nov 2022), interviewers want to know if you&#8217;re aware of the shift to Duende IdentityServer and its <strong>licensing change</strong> (free for dev/small-scale, paid for production at scale).</li>



<li><strong>Assesses architecture understanding</strong> — configuring Clients, Scopes, and Resources correctly requires understanding the full OAuth2/OIDC flow, not just theory.</li>



<li><strong>Security depth</strong> — token validation, secure redirect URIs, and grant type selection are common real-world pitfalls; this question filters candidates who understand <em>why</em>, not just <em>how</em>.</li>
</ol>



<p><b>67. What is an API scope ? What is an API resource ?</b></p>
<p>These two concepts are often confused but serve different purposes in modeling what a client can access.</p>



<h3 class="wp-block-heading">API Resource:</h3>



<p class="wp-block-paragraph">An <strong>API Resource</strong> represents the actual <strong>API/application</strong> you want to protect — a logical grouping of one or more related APIs that validate tokens.</p>



<ul class="wp-block-list">
<li>Think of it as the &#8220;owner&#8221; or &#8220;container&#8221; for one or more scopes.</li>



<li>It typically corresponds to a physical API (e.g., <code>OrdersApi</code>, <code>PaymentsApi</code>).</li>



<li>A single API Resource can expose <strong>multiple scopes</strong>.</li>



<li>Access tokens are validated against the API Resource (via its name or associated scopes), and the API checks the token&#8217;s <code>aud</code> (audience) / scope claims.</li>
</ul>



<pre class="wp-block-code"><code>new ApiResource("orders-api", "Orders API")
{
    Scopes = { "orders.read", "orders.write" }
}</code></pre>



<h3 class="wp-block-heading">API Scope</h3>



<p class="wp-block-paragraph">An <strong>API Scope</strong> represents a specific <strong>permission or capability</strong> that a client can request — a granular piece of access within an API Resource.</p>



<ul class="wp-block-list">
<li>It&#8217;s what actually appears in the <strong>access token&#8217;s <code>scope</code> claim</strong>.</li>



<li>Clients request specific scopes; the API checks if the token contains the required scope before allowing an operation.</li>



<li>Naming convention is often <code>resource.action</code>, like <code>orders.read</code>, <code>orders.write</code>, <code>payments.refund</code>.</li>
</ul>



<pre class="wp-block-code"><code>new ApiScope("orders.read", "Read access to Orders"),
new ApiScope("orders.write", "Write access to Orders")</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">How They Relate</span>:</p>



<pre class="wp-block-code"><code>API Resource: "orders-api"
   ├── Scope: "orders.read"
   └── Scope: "orders.write"</code></pre>



<ul class="wp-block-list">
<li>A <strong>client</strong> requests one or more <strong>scopes</strong> (<code>orders.read</code>).</li>



<li>The <strong>Authorization Server</strong> (e.g., Duende IdentityServer) checks if the client is allowed those scopes, then issues an <strong>access token</strong> containing them.</li>



<li>The <strong>API Resource</strong> (<code>orders-api</code>) validates the incoming token — checking that the token&#8217;s audience/scopes match what it expects.</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Example in Duende IdentityServer Config</span></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public static IEnumerable&lt;ApiScope&gt; ApiScopes =&gt;
    new List&lt;ApiScope&gt;
    {
        new ApiScope(&quot;orders.read&quot;, &quot;Read Orders&quot;),
        new ApiScope(&quot;orders.write&quot;, &quot;Write Orders&quot;)
    };

public static IEnumerable&lt;ApiResource&gt; ApiResources =&gt;
    new List&lt;ApiResource&gt;
    {
        new ApiResource(&quot;orders-api&quot;, &quot;Orders API&quot;)
        {
            Scopes = { &quot;orders.read&quot;, &quot;orders.write&quot; }
        }
    };
</pre></div>


<p><b>68. What is a client in IdentityServer ? What is a client ID and client secret ?</b></p>



<p class="wp-block-paragraph">A <strong>Client</strong> in IdentityServer (Duende IdentityServer) represents an <strong>application</strong> that is registered to request tokens from the IdentityServer — i.e., anything that wants to authenticate a user or get access to an API on behalf of a user (or itself) must be configured as a <strong>Client</strong>.</p>



<ul class="wp-block-list">
<li>Examples of clients: a web app, a mobile app, a SPA, a backend service (M2M).</li>



<li>Each client has its own configuration defining <strong>what it&#8217;s allowed to do</strong> — which grant types it can use, which scopes it can request, where it can redirect to after login, token lifetimes, etc.</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Example:</span></p>



<pre class="wp-block-code"><code>new Client
{
    ClientId = "webapp-client",
    ClientSecrets = { new Secret("supersecret".Sha256()) },
    AllowedGrantTypes = GrantTypes.Code,
    RedirectUris = { "https://myapp.com/signin-oidc" },
    AllowedScopes = { "openid", "profile", "orders.read" },
    RequirePkce = true
}</code></pre>



<h3 class="wp-block-heading">Client ID</h3>



<p class="wp-block-paragraph">The <strong>Client ID</strong> is a <strong>public, unique identifier</strong> for the client application registered with the IdentityServer.</p>



<ul class="wp-block-list">
<li>Think of it like a <strong>username</strong> for the application (not secret — it&#8217;s often visible in browser URLs, JS code, mobile app binaries).</li>



<li>IdentityServer uses it to look up the client&#8217;s configuration (allowed scopes, grant types, redirect URIs, etc.) when a token request comes in.</li>
</ul>



<pre class="wp-block-code"><code>ClientId = "webapp-client"</code></pre>



<h3 class="wp-block-heading">Client Secret</h3>



<p class="wp-block-paragraph">The <strong>Client Secret</strong> is a <strong>confidential credential</strong> (like a password) that proves the client&#8217;s identity to the IdentityServer — used only by <strong>confidential clients</strong> (server-side apps that can safely store secrets).</p>



<ul class="wp-block-list">
<li>It&#8217;s exchanged along with the Client ID when requesting a token, so IdentityServer can verify the request is genuinely coming from that registered client (not an impersonator).</li>



<li><strong>Never used in public clients</strong> like SPAs or mobile apps, since those can&#8217;t securely store a secret (anyone could extract it from client-side code) — that&#8217;s why <strong>PKCE</strong> replaces the need for a client secret in public clients.</li>
</ul>



<pre class="wp-block-code"><code>ClientSecrets = { new Secret("supersecret".Sha256()) }</code></pre>



<p class="wp-block-paragraph">Think of <strong>Client ID</strong> as a <strong>username</strong> and <strong>Client Secret</strong> as a <strong>password</strong> — but for an <em>application</em>, not a human user. Just like a user proves who they are with a username/password, a confidential client app proves its identity to IdentityServer with a Client ID/Client Secret pair before it&#8217;s trusted to receive tokens.</p>



<p><b>69. What is an identity resource ?</b></p>



<p class="wp-block-paragraph">An <strong>Identity Resource</strong> in IdentityServer (OIDC) represents a set of <strong>claims about a user&#8217;s identity</strong> — such as their name, email, or profile info — that a client can request access to via <strong>scopes</strong>. While <strong>API Resources/Scopes</strong> control access to <em>APIs</em>, <strong>Identity Resources</strong> control access to <em>user identity data</em> returned in the <strong>ID Token</strong>.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why It Exists</span></p>



<p class="wp-block-paragraph">When a client authenticates a user via OpenID Connect, it doesn&#8217;t just want a plain &#8220;yes, logged in&#8221; — it usually wants some information <em>about</em> the user (name, email, etc.). Identity Resources define exactly <strong>which claims</strong> get bundled together and exposed as a requestable scope.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Standard (Built-in) Identity Resources</span></p>



<p class="wp-block-paragraph">IdentityServer ships with standard OIDC identity resources out of the box:</p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Identity Resource</th><th>Claims Included</th></tr></thead><tbody><tr><td><code>openid</code></td><td><code>sub</code> (subject/user ID) — <strong>required</strong> for any OIDC request</td></tr><tr><td><code>profile</code></td><td><code>name</code>, <code>family_name</code>, <code>given_name</code>, <code>picture</code>, <code>birthdate</code>, etc.</td></tr><tr><td><code>email</code></td><td><code>email</code>, <code>email_verified</code></td></tr><tr><td><code>address</code></td><td><code>address</code></td></tr><tr><td><code>phone</code></td><td><code>phone_number</code>, <code>phone_number_verified</code></td></tr></tbody></table></div>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Custom Identity Resource Example</span></p>



<p class="wp-block-paragraph">You can also define your own, e.g., to expose a custom <code>role</code> or <code>department</code> claim:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public static IEnumerable&lt;IdentityResource&gt; IdentityResources =&gt;
    new List&lt;IdentityResource&gt;
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResource(
            name: &quot;roles&quot;,
            displayName: &quot;User Roles&quot;,
            claimTypes: new&#x5B;] { &quot;role&quot; })
    };
</pre></div>


<p class="wp-block-paragraph"><span style="text-decoration: underline;">How It&#8217;s Used</span>:</p>



<ol class="wp-block-list">
<li>Client requests scopes including an identity resource: <code>openid profile roles</code>.</li>



<li>User logs in and consents.</li>



<li>IdentityServer issues an <strong>ID Token</strong> containing the claims tied to those identity resources (<code>sub</code>, <code>name</code>, <code>role</code>, etc.).</li>



<li>Client reads these claims to know <em>who</em> the user is.</li>
</ol>



<pre class="wp-block-code"><code>AllowedScopes = { "openid", "profile", "roles", "orders.read" }</code></pre>



<p class="wp-block-paragraph">(Notice <code>orders.read</code> here is an <strong>API Scope</strong>, while <code>openid</code>, <code>profile</code>, <code>roles</code> are <strong>Identity Resources</strong> — both can be requested together in the same token request.)</p>



<p><b>70. What does the openid scope do ?</b></p>
<p>The openid scope is the switch that turns on OpenID Connect authentication — it&#8217;s mandatory for any login flow, triggers the issuance of an ID Token, and adds the sub claim (unique user identifier) to that token. Without it, you&#8217;re doing plain OAuth 2.0 authorization with no guaranteed identity information.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What It Actually Does</span></p>



<ol class="wp-block-list">
<li><strong>Activates OIDC behavior</strong> — without <code>openid</code>, the request is treated as plain OAuth 2.0 (authorization only). Including it tells IdentityServer: <em>&#8220;This is an OpenID Connect request — I need to know who the user is, not just what they can access.&#8221;</em></li>



<li><strong>Triggers ID Token issuance</strong> — when <code>openid</code> is requested (and the flow completes), the token response includes an <strong>ID Token</strong> (a JWT) alongside any access token.</li>



<li><strong>Includes the <code>sub</code> claim</strong> — the <code>openid</code> scope maps to the <code>IdentityResources.OpenId</code> identity resource, which contains exactly one claim: <code>sub</code> (subject identifier — a unique, stable ID for the authenticated user).</li>
</ol>



<p><b>71. What does the profile scope do ?</b></p>
<p>The <code>profile</code> scope is a standard Identity Resource that bundles general user profile claims (<code>name</code>, <code>given_name</code>, <code>picture</code>, etc.) into the ID Token — but it only returns claims that are actually populated for the user via your <code>IProfileService</code> or identity store.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What It Adds</span></p>



<p class="wp-block-paragraph">When a client includes <code>profile</code> in its scope request (alongside the mandatory <code>openid</code>), IdentityServer includes these claims in the <strong>ID Token</strong> (or makes them available via the <code>/connect/userinfo</code> endpoint), <em>if</em> the user has values for them:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Claim</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td>Full display name</td></tr><tr><td><code>given_name</code></td><td>First name</td></tr><tr><td><code>family_name</code></td><td>Last name</td></tr><tr><td><code>middle_name</code></td><td>Middle name</td></tr><tr><td><code>nickname</code></td><td>Casual/preferred name</td></tr><tr><td><code>preferred_username</code></td><td>Preferred username/handle</td></tr><tr><td><code>profile</code></td><td>URL to the user&#8217;s profile page</td></tr><tr><td><code>picture</code></td><td>URL to the user&#8217;s profile photo</td></tr><tr><td><code>website</code></td><td>User&#8217;s website URL</td></tr><tr><td><code>gender</code></td><td>Gender</td></tr><tr><td><code>birthdate</code></td><td>Date of birth</td></tr><tr><td><code>zoneinfo</code></td><td>Time zone</td></tr><tr><td><code>locale</code></td><td>Locale/language preference</td></tr><tr><td><code>updated_at</code></td><td>When profile info was last updated</td></tr></tbody></table></figure>



<p><b>72. What is the sub claim ?</b></p>



<p class="wp-block-paragraph">The <strong><code>sub</code></strong> (subject) claim is the <strong>unique, stable identifier for the authenticated user</strong> — it&#8217;s the single most important claim in OpenID Connect, and the only claim guaranteed to be present whenever the <code>openid</code> scope is requested.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What It Represents:</span></p>



<ul class="wp-block-list">
<li><code>sub</code> answers the question: <strong>&#8220;Which user is this, exactly?&#8221;</strong></li>



<li>It&#8217;s typically a unique ID from the identity provider&#8217;s user store (e.g., a GUID or primary key), <strong>not</strong> the user&#8217;s email or username — because emails/usernames can change, but <code>sub</code> must remain constant for the lifetime of that user&#8217;s account.</li>
</ul>



<pre class="wp-block-code"><code>{
  "sub": "8f14e45f-ceea-467a-9575-9a61a1c8b1c5",
  "name": "John Doe",
  "email": "john@example.com"
}</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why It Matters:</span></p>



<ol class="wp-block-list">
<li><strong>Primary key for identity</strong> — your application should use <code>sub</code> (not <code>email</code> or <code>name</code>) as the key to look up/link the local user record, since it&#8217;s immutable and unique.</li>



<li><strong>Guaranteed presence</strong> — every ID Token issued for an <code>openid</code> scope request contains <code>sub</code>, even if no other claims are requested.</li>



<li><strong>Uniqueness scope</strong> — <code>sub</code> is guaranteed unique <strong>within the issuer (<code>iss</code>)</strong> — i.e., unique per identity provider. The combination of <code>iss</code> + <code>sub</code> is what truly guarantees global uniqueness (important when supporting multiple identity providers/federation).</li>
</ol>



<p><b>73. What is a signing credential ?</b></p>



<p class="wp-block-paragraph">A <strong>Signing Credential</strong> is the <strong>cryptographic key (and algorithm)</strong> that IdentityServer (or any OIDC/OAuth token issuer) uses to <strong>digitally sign</strong> the tokens it issues — primarily the <strong>ID Token</strong> and, optionally, the <strong>Access Token</strong> (when using JWT format). It&#8217;s what allows clients and APIs to <strong>verify that a token is genuine and hasn&#8217;t been tampered with</strong>.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why It&#8217;s Needed</span></p>



<p class="wp-block-paragraph">Tokens (JWTs) are just Base64-encoded JSON — anyone can read or even <em>forge</em> one if there&#8217;s no way to verify authenticity. The <strong>signing credential</strong> solves this: IdentityServer signs the token with a <strong>private key</strong>, and consumers (clients/APIs) verify the signature using the corresponding <strong>public key</strong> — proving the token really came from that trusted issuer and wasn&#8217;t altered in transit.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">How It Works (Simplified)</span></p>



<ol class="wp-block-list">
<li>IdentityServer creates a token (header + payload).</li>



<li>It signs the token using its <strong>private key</strong> → produces the token&#8217;s <strong>signature</strong>.</li>



<li>The signed JWT (<code>header.payload.signature</code>) is sent to the client.</li>



<li>The client/API fetches IdentityServer&#8217;s <strong>public key</strong> (via the discovery/JWKS endpoint) and <strong>verifies the signature</strong>.</li>



<li>If the signature is valid → token is trusted, unmodified, and genuinely issued by that server.</li>
</ol>



<pre class="wp-block-code"><code>JWT = Base64(Header) + "." + Base64(Payload) + "." + Signature
Signature = Sign(Header + Payload, PrivateKey)</code></pre>



<p><b>74. What is AddDeveloperSigningCredential() ? Why should developer signing credentials not be used in production ?</b></p>



<p class="wp-block-paragraph"><code>AddDeveloperSigningCredential()</code> is a <strong>convenience method</strong> in Duende IdentityServer used to quickly generate a <strong>temporary RSA signing key</strong> for <strong>local development and testing</strong>, so you don&#8217;t have to set up a real X.509 certificate just to get IdentityServer running.</p>



<pre class="wp-block-code"><code>builder.Services.AddIdentityServer()
    .AddDeveloperSigningCredential();</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What It Actually Does</span></p>



<ol class="wp-block-list">
<li>On first run, it generates an <strong>RSA key pair</strong> and serializes it to a file on disk (typically <code>tempkey.rsa</code> or <code>tempkey.jwk</code> in the app&#8217;s root directory).</li>



<li>On subsequent runs, it <strong>reuses the same key</strong> from that file (so tokens remain valid across restarts <em>during development</em>, as long as the file persists).</li>



<li>This key is used to sign ID Tokens and JWT access tokens, just like a &#8220;real&#8221; signing credential would.</li>
</ol>



<h3 class="wp-block-heading">Why It Must NOT Be Used in Production</h3>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">a. Key Is Not Securely Managed</span></p>



<p class="wp-block-paragraph">The key is stored as a <strong>plain file on the local disk</strong> of the server — no encryption, no access control, no integration with a proper secrets/key management system (like Azure Key Vault, AWS KMS, or an HSM). Anyone with file system access can read the private key and forge valid tokens.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">b. Not Portable Across Multiple Instances/Servers</span></p>



<p class="wp-block-paragraph">In production, you typically run <strong>multiple instances</strong> of your app (load-balanced, containerized, auto-scaled). Since the key file is generated <strong>locally per instance</strong>, each instance would have a <strong>different signing key</strong> — meaning a token issued by Instance A would <strong>fail validation</strong> when checked against Instance B&#8217;s public key. This breaks authentication in any horizontally-scaled deployment.</p>



<pre class="wp-block-code"><code>Instance A (Key A) → issues token → 
Instance B (Key B) → tries to validate → ❌ Signature mismatch</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">c. Key Regeneration Invalidates Existing Tokens</span></p>



<p class="wp-block-paragraph">If the key file is lost, deleted, or the app is redeployed to a fresh container/environment without persisting that file (very common in containerized/cloud deployments), a <strong>new key is generated</strong> — instantly invalidating <strong>all previously issued tokens</strong>. Users get logged out unexpectedly; APIs start rejecting valid-looking tokens.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">d. No Key Rotation Support</span></p>



<p class="wp-block-paragraph">Production systems need <strong>key rotation</strong> (periodically retiring old keys while still honoring recently-issued tokens signed with them, using multiple keys in the JWKS). <code>AddDeveloperSigningCredential()</code> has no concept of rotation — it&#8217;s a single, static key with no lifecycle management.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">e. Security/Compliance Red Flag</span></p>



<p class="wp-block-paragraph">Using a developer-only feature in production is a clear signal of misconfiguration — it may fail security audits/pen tests outright, since the key isn&#8217;t protected by any real key management or rotation policy.</p>



<h3 class="wp-block-heading">What to Use Instead in Production</h3>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Approach</th><th>Description</th></tr></thead><tbody><tr><td><strong>X.509 Certificate</strong></td><td>Load from certificate store or file (<code>AddSigningCredential(certificate)</code>)</td></tr><tr><td><strong>Azure Key Vault</strong></td><td>Store keys securely, integrate via <code>AddSigningCredential</code> with a Key Vault-backed provider</td></tr><tr><td><strong>AWS KMS / HSM</strong></td><td>Hardware-backed key management for high-security environments</td></tr><tr><td><strong>Automatic Key Management (Duende)</strong></td><td>Duende IdentityServer&#8217;s built-in feature to automatically generate, rotate, and publish signing keys securely — recommended modern approach</td></tr></tbody></table></div>



<p><b>75. What is tempkey.jwk ?</b></p>



<p class="wp-block-paragraph"><code>tempkey.jwk</code> is the physical JSON file where <code>AddDeveloperSigningCredential()</code> stores its auto-generated RSA private/public key pair on disk — convenient for local development, but a common source of production bugs (like users getting logged out on every deploy) since it&#8217;s not portable, persistent, or securely managed across multiple instances.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">What&#8217;s Inside It:</span></p>



<p class="wp-block-paragraph">The file contains a serialized <strong>JWK (JSON Web Key)</strong> — a standard JSON format for representing a cryptographic key. It holds the <strong>RSA private key</strong> (and its corresponding public key components) that IdentityServer uses to sign tokens.</p>



<pre class="wp-block-code"><code>{
  "kty": "RSA",
  "kid": "a3f8c9e2b1d4...",
  "use": "sig",
  "alg": "RS256",
  "n": "xGOr-H7A-PWc7uxi...",   // modulus (part of public key)
  "e": "AQAB",                  // exponent (part of public key)
  "d": "X4cTteJY_gn4FYPsXB8r...", // private exponent — THE SECRET
  "p": "...", "q": "...", "dp": "...", "dq": "...", "qi": "..."  // other RSA private key components
}</code></pre>



<ul class="wp-block-list">
<li><code>kty</code> — key type (<code>RSA</code>)</li>



<li><code>kid</code> — key ID (used to match the right key when multiple keys exist)</li>



<li><code>alg</code> — signing algorithm (<code>RS256</code>)</li>



<li><code>n</code>, <code>e</code> — the <strong>public key</strong> components (safe to share)</li>



<li><code>d</code>, <code>p</code>, <code>q</code>, etc. — the <strong>private key</strong> components (must stay secret — this is what signs tokens)</li>
</ul>



<p><b>76. What is the difference between an Access token and Refresh token ?</b></p>



<p class="wp-block-paragraph">Both are issued by the Authorization Server, but they serve <strong>completely different purposes</strong> in the OAuth 2.0 flow — one is for <em>using</em> an API, the other is for <em>getting new access without re-login</em>.</p>



<h3 class="wp-block-heading">Access Token</h3>



<p class="wp-block-paragraph">The <strong>Access Token</strong> is the credential a client presents to a <strong>Resource Server (API)</strong> to access protected resources.</p>



<ul class="wp-block-list">
<li><strong>Purpose</strong>: Authorization — proves the client has permission to call the API with certain scopes.</li>



<li><strong>Lifetime</strong>: <strong>Short-lived</strong> (typically minutes to a few hours) — by design, to limit damage if it&#8217;s leaked/stolen.</li>



<li><strong>Sent to</strong>: Resource Server / API (in the <code>Authorization: Bearer &lt;token&gt;</code> header).</li>



<li><strong>Format</strong>: Can be a <strong>JWT</strong> (self-contained, verifiable via signature) or an <strong>opaque/reference token</strong> (API must call back to the Authorization Server to validate it via introspection).</li>



<li><strong>Contains</strong>: Scopes, expiry (<code>exp</code>), issuer (<code>iss</code>), audience (<code>aud</code>), and sometimes user claims.</li>
</ul>



<pre class="wp-block-code"><code>Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6...</code></pre>



<h3 class="wp-block-heading">Refresh Token</h3>



<p class="wp-block-paragraph">The <strong>Refresh Token</strong> is a long-lived credential used <strong>only</strong> to obtain a <strong>new Access Token</strong> (and often a new Refresh Token) once the current Access Token expires — <strong>without requiring the user to log in again</strong>.</p>



<ul class="wp-block-list">
<li><strong>Purpose</strong>: Session continuity — silent renewal of access.</li>



<li><strong>Lifetime</strong>: <strong>Long-lived</strong> (days, weeks, or even until explicitly revoked).</li>



<li><strong>Sent to</strong>: <strong>Only</strong> the Authorization Server&#8217;s token endpoint — <strong>never</strong> sent to APIs/Resource Servers.</li>



<li><strong>Format</strong>: Almost always <strong>opaque</strong> (a random string) — not meant to be decoded/inspected by clients.</li>



<li><strong>Higher sensitivity</strong>: Since it can generate new access indefinitely, it must be stored <strong>very securely</strong> (e.g., HTTP-only secure cookie or server-side storage — never in browser <code>localStorage</code> for SPAs).</li>
</ul>



<pre class="wp-block-code"><code>POST /connect/token
grant_type=refresh_token
&amp;refresh_token=8xLOxBtZp8...
&amp;client_id=webapp-client
&amp;client_secret=...</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why Have Both?</span></p>



<p class="wp-block-paragraph">This two-token design balances <strong>security</strong> and <strong>user experience</strong>:</p>



<ul class="wp-block-list">
<li><strong>Short-lived Access Tokens</strong> minimize the attack window if intercepted.</li>



<li><strong>Long-lived Refresh Tokens</strong> avoid forcing users to log in every few minutes, while still letting the Authorization Server <strong>revoke access at any time</strong> (e.g., on logout, password change, or suspicious activity) by invalidating the refresh token — which immediately stops any further access token renewal.</li>
</ul>



<h2>ASP.NET CORE Performance based Interview Questions</h2>
<p>ASP.NET Core performance-based interview questions matter because performance problems are among the most common and costly issues in production systems, and these questions reveal whether a candidate truly understands why the framework behaves the way it does — not just how to write functionally correct code. Topics like avoiding blocking async calls, caching (in-memory/distributed), efficient EF Core usage (AsNoTracking(), avoiding N+1 queries), and memory/GC awareness reflect real bottlenecks that only surface under actual load. A candidate who can reason about these trade-offs likely has hands-on experience diagnosing slow endpoints or memory issues in a live system — which is why employers use these questions as a strong signal of production readiness, not just theoretical knowledge.</p>



<p><b>77. What is caching ? What caching mechanisms are available in ASP.NET Core ?</b></p>



<p class="wp-block-paragraph"><strong>Caching</strong> is the practice of storing frequently accessed or expensive-to-compute data in a temporary, fast-access storage layer, so subsequent requests for that same data can be served quickly — without repeating the expensive operation (e.g., a database query, an API call, or heavy computation).</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why It Matters:</span></p>



<ul class="wp-block-list">
<li>Reduces <strong>latency</strong> (faster response times)</li>



<li>Reduces <strong>load</strong> on databases/downstream services</li>



<li>Improves <strong>scalability</strong> — the same server can handle more requests</li>



<li>Trade-off: cached data can become <strong>stale</strong>, so cache invalidation/expiration strategy matters</li>
</ul>



<h3 class="wp-block-heading">Caching Mechanisms in ASP.NET Core</h3>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">a. In-Memory Caching (<code>IMemoryCache</code>)</span></p>



<p class="wp-block-paragraph">Stores data <strong>in the web server&#8217;s own process memory</strong>. Fast, but not shared across multiple server instances.</p>



<pre class="wp-block-code"><code>builder.Services.AddMemoryCache();

public class ProductService
{
    private readonly IMemoryCache _cache;
    public ProductService(IMemoryCache cache) =&gt; _cache = cache;

    public Product GetProduct(int id)
    {
        return _cache.GetOrCreate($"product_{id}", entry =&gt;
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
            return _repository.GetById(id); // expensive call, only runs on cache miss
        });
    }
}</code></pre>



<p class="wp-block-paragraph"><strong>Best for</strong>: single-server apps, or data that&#8217;s okay to differ slightly between instances.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">b. Distributed Caching (<code>IDistributedCache</code>)</span></p>



<p class="wp-block-paragraph">Stores data in an <strong>external, shared cache</strong> (e.g., Redis, SQL Server) — consistent across <strong>multiple server instances</strong> (critical for load-balanced/scaled-out apps).</p>



<pre class="wp-block-code"><code>builder.Services.AddStackExchangeRedisCache(options =&gt;
{
    options.Configuration = "localhost:6379";
});

public async Task&lt;string&gt; GetDataAsync(string key)
{
    var cached = await _distributedCache.GetStringAsync(key);
    if (cached != null) return cached;

    var data = await FetchExpensiveDataAsync();
    await _distributedCache.SetStringAsync(key, data,
        new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });
    return data;
}</code></pre>



<p class="wp-block-paragraph"><strong>Best for</strong>: multi-server/cloud deployments where all instances must see the same cached data.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">c. Response Caching (<code>Response Caching Middleware</code>)</span></p>



<p class="wp-block-paragraph">Caches the <strong>entire HTTP response</strong> (based on headers like <code>Cache-Control</code>), so identical requests are served directly — often from the client/browser or a proxy — without hitting the server logic at all.</p>



<pre class="wp-block-code"><code>builder.Services.AddResponseCaching();
app.UseResponseCaching();

&#91;HttpGet]
&#91;ResponseCache(Duration = 60)]
public IActionResult GetProducts() =&gt; Ok(_products);</code></pre>



<p class="wp-block-paragraph"><strong>Best for</strong>: public, non-personalized GET endpoints (e.g., product listings).</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">d. Output Caching (ASP.NET Core 7+)</span></p>



<p class="wp-block-paragraph">A newer, more flexible server-side caching mechanism than Response Caching — supports <strong>tag-based invalidation</strong>, policies, and varying by query string/headers, without relying solely on HTTP cache headers.</p>



<pre class="wp-block-code"><code>builder.Services.AddOutputCache(options =&gt;
{
    options.AddPolicy("Expire60", b =&gt; b.Expire(TimeSpan.FromSeconds(60)));
});
app.UseOutputCache();

app.MapGet("/products", GetProducts).CacheOutput("Expire60");</code></pre>



<p class="wp-block-paragraph"><strong>Best for</strong>: modern apps needing fine-grained control (tag invalidation, per-endpoint policies) — generally preferred over Response Caching in .NET 7+.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Quick Comparison:</span></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Mechanism</th><th>Storage Location</th><th>Shared Across Instances?</th><th>Caches What</th></tr></thead><tbody><tr><td><code>IMemoryCache</code></td><td>Local server memory</td><td>❌ No</td><td>Any object</td></tr><tr><td><code>IDistributedCache</code></td><td>External (Redis/SQL)</td><td>✅ Yes</td><td>Any object (serialized)</td></tr><tr><td>Response Caching</td><td>HTTP layer (client/proxy/server)</td><td>Depends on layer</td><td>Full HTTP response</td></tr><tr><td>Output Caching</td><td>Server-side (pluggable store)</td><td>✅ Yes (with Redis backing)</td><td>Full HTTP response, more flexible</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why Interviewers Ask This:</span></p>



<ol class="wp-block-list">
<li>Tests whether you know <strong>when to use which</strong> — a very common real-world mistake is using <code>IMemoryCache</code> in a load-balanced multi-instance deployment, causing inconsistent data across servers.</li>



<li>Checks awareness of <strong>cache invalidation</strong> challenges — &#8220;there are only two hard problems in computer science: cache invalidation and naming things.&#8221;</li>



<li>Follow-up likely: <em>&#8220;How would you handle cache invalidation when the underlying data changes?&#8221;</em> → Explicitly remove/update the cache entry on write (<code>Cache-Aside</code> pattern), or use short expiration + tag-based invalidation (Output Caching supports this natively).</li>
</ol>



<p><b>78. When should you use Redis ?</b></p>



<p class="wp-block-paragraph"><strong>Redis</strong> is an in-memory, distributed data store — use it when you need <strong>fast, shared state across multiple servers/instances</strong>, which <code>IMemoryCache</code> alone can&#8217;t provide.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Key Scenarios:</span></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Use Case</th><th>Why Redis Fits</th></tr></thead><tbody><tr><td><strong>Distributed caching</strong></td><td>Multiple app instances (load-balanced/scaled-out) need to share the same cached data consistently</td></tr><tr><td><strong>Session state</strong></td><td>Store user sessions centrally so any server instance can handle any request (<code>AddStackExchangeRedisCache</code> for <code>IDistributedCache</code>)</td></tr><tr><td><strong>Rate limiting / counters</strong></td><td>Atomic increment operations (<code>INCR</code>) across distributed requests</td></tr><tr><td><strong>Pub/Sub messaging</strong></td><td>Real-time notifications, SignalR backplane for scaled-out WebSocket connections</td></tr><tr><td><strong>Short-lived, high-throughput data</strong></td><td>Leaderboards, temporary tokens, distributed locks — Redis is extremely fast (in-memory)</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">When NOT to Use Redis:</span></p>



<ul class="wp-block-list">
<li><strong>Single-instance apps</strong> — <code>IMemoryCache</code> is simpler and faster (no network hop).</li>



<li><strong>Long-term persistent storage</strong> — Redis is primarily in-memory; not a replacement for a relational/durable database (though it supports optional persistence).</li>



<li><strong>Complex relational queries</strong> — Redis is a key-value store, not suited for relational joins/reporting.</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why Interviewers Ask This:</span></p>



<p class="wp-block-paragraph">Tests whether you understand <strong>the actual problem Redis solves</strong> — shared, fast state across a distributed system — rather than just knowing &#8220;Redis = caching.&#8221; A common follow-up: <em>&#8220;Why not just use <code>IMemoryCache</code> everywhere?&#8221;</em> → Because in a scaled-out deployment, each instance would have its <strong>own</strong> separate cache, causing inconsistent data (e.g., a user&#8217;s session existing on Server A but not Server B).</p>



<p class="wp-block-paragraph"><strong>In short</strong>: Use Redis when you need <strong>fast, shared state across multiple app instances</strong> — distributed caching, centralized session storage, SignalR backplane, or atomic counters — not for single-server apps or as a primary relational database.</p>



<p><b>79. What is cache invalidation ? What is cache stampede ?</b></p>
<p><b>Cache invalidation</b> is explicitly clearing/updating stale cache data when the source changes (via TTL, cache-aside, or write-through). <b>Cache stampede</b> is when many concurrent requests simultaneously regenerate the same expired cache entry, overwhelming the backend — prevented via locking, background refresh, or staggered expiration.</p>
<p><b>80. How can caching improves performance ?</b></p>



<p class="wp-block-paragraph">Caching improves performance by <strong>avoiding repeated, expensive work</strong> — serving data from a fast storage layer instead of recomputing or re-fetching it every time.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Key Ways It Helps:</span></p>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Benefit</th><th>Explanation</th></tr></thead><tbody><tr><td><strong>Reduces latency</strong></td><td>Reading from memory (or Redis) is far faster than querying a database, calling an external API, or running heavy computation</td></tr><tr><td><strong>Reduces database/backend load</strong></td><td>Fewer repeated queries hit the database, freeing it up to handle other work — critical under high traffic</td></tr><tr><td><strong>Reduces network calls</strong></td><td>Avoids redundant calls to slow external services/APIs</td></tr><tr><td><strong>Improves scalability</strong></td><td>Since less work is repeated per request, the same server resources can handle more concurrent users</td></tr><tr><td><strong>Reduces CPU usage</strong></td><td>Avoids re-running expensive computations (e.g., complex aggregations, report generation)</td></tr></tbody></table></div>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Example:</span></p>



<pre class="wp-block-code"><code>// Without caching: every request hits the database
public Product GetProduct(int id) =&gt; _repository.GetById(id); // DB call every time

// With caching: DB is hit once, then served from memory
public Product GetProduct(int id) =&gt;
    _cache.GetOrCreate($"product_{id}", entry =&gt;
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        return _repository.GetById(id); // only runs on cache miss
    });</code></pre>



<p class="wp-block-paragraph">If this endpoint gets <strong>10,000 requests</strong> in 10 minutes for the same product, without caching that&#8217;s <strong>10,000 database queries</strong>; with caching, it&#8217;s just <strong>1 database query</strong> — the rest are served instantly from memory.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Where It Matters Most:</span></p>



<ul class="wp-block-list">
<li><strong>Read-heavy, rarely-changing data</strong> (product catalogs, configuration, reference data) — biggest win</li>



<li><strong>Expensive computations</strong> (reports, aggregations) — cache the result, not just raw data</li>



<li><strong>External API calls</strong> — especially rate-limited or slow third-party services</li>
</ul>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">The Trade-off:</span></p>



<p class="wp-block-paragraph">Caching trades <strong>freshness</strong> for <strong>speed</strong> — cached data can become stale until it&#8217;s invalidated/expired, so it&#8217;s best suited for data that doesn&#8217;t need to be instantly up-to-date on every single request.</p>



<p><b>81. What is rate limiting ? Why would you use rate limiting in an API ? What HTTP status code is normally returned when rate limiting rejects a request?</b></p>



<p class="wp-block-paragraph"><strong>Rate limiting</strong> is a technique used to control <strong>how many requests</strong> a client (identified by IP, user, API key, etc.) can make to an API within a given time window — rejecting or delaying requests that exceed the allowed threshold.</p>



<h3 class="wp-block-heading">Why Use Rate Limiting in an API?</h3>



<div class="table-responsive"><table class="table table-striped table-bordered"><thead><tr class="table-primary"><th>Reason</th><th>Explanation</th></tr></thead><tbody><tr><td><strong>Prevent abuse/DoS attacks</strong></td><td>Stops a single client from overwhelming the server with excessive requests, intentionally or accidentally</td></tr><tr><td><strong>Ensure fair usage</strong></td><td>Prevents one client from monopolizing shared resources, so others get fair access</td></tr><tr><td><strong>Protect backend resources</strong></td><td>Shields databases/downstream services from being overloaded by request spikes</td></tr><tr><td><strong>Cost control</strong></td><td>Limits usage of paid/metered resources (e.g., third-party API calls, compute-heavy endpoints)</td></tr><tr><td><strong>Enforce business/pricing tiers</strong></td><td>Different rate limits for free vs. paid API tiers</td></tr></tbody></table></div>



<h3 class="wp-block-heading">HTTP Status Code</h3>



<p class="wp-block-paragraph">When a request is rejected due to rate limiting, the API should return:</p>



<pre class="wp-block-code"><code>429 Too Many Requests</code></pre>



<p class="wp-block-paragraph">This is the standard status code (RFC 6585) indicating the client has sent too many requests in a given time period. It&#8217;s common to also include a <code>Retry-After</code> header telling the client how long to wait before retrying.</p>



<pre class="wp-block-code"><code>HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{
  "error": "Rate limit exceeded. Try again in 30 seconds."
}</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Example in ASP.NET Core (Built-in Rate Limiting Middleware, .NET 7+)</span></p>



<pre class="wp-block-code"><code>builder.Services.AddRateLimiter(options =&gt;
{
    options.AddFixedWindowLimiter("fixed", opt =&gt;
    {
        opt.PermitLimit = 100;
        opt.Window = TimeSpan.FromMinutes(1);
        opt.QueueLimit = 0; // no queueing, reject immediately over limit
    });

    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});

app.UseRateLimiter();

app.MapGet("/api/data", () =&gt; "response")
   .RequireRateLimiting("fixed");</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why Interviewers Ask This:</span></p>



<ol class="wp-block-list">
<li><strong>Tests API design/security awareness</strong> — rate limiting is a fundamental protection mechanism that many junior developers overlook until an incident forces it.</li>



<li><strong>Standards knowledge</strong> — knowing the correct status code (<code>429</code>, not <code>403</code> or <code>503</code>) shows familiarity with HTTP semantics, not just &#8220;it returns an error.&#8221;</li>



<li><strong>Follow-up likely</strong>: <em>&#8220;What&#8217;s the difference between rate limiting and throttling?&#8221;</em> → Rate limiting typically <strong>rejects</strong> excess requests outright (429), while throttling may <strong>delay/queue</strong> them to smooth out load instead of rejecting immediately — ASP.NET Core&#8217;s rate limiter supports both behaviors depending on configuration (e.g., <code>QueueLimit</code> &gt; 0 allows queuing instead of immediate rejection).</li>
</ol>



<p><b>82. What rate limiter algorithms are available in ASP.NET Core?</b></p>



<p class="wp-block-paragraph">ASP.NET Core&#8217;s built-in <strong>Rate Limiting Middleware</strong> (introduced in .NET 7, via <code>System.Threading.RateLimiting</code>) provides <strong>four</strong> algorithms, each suited to different traffic patterns.</p>



<h3 class="wp-block-heading">1. Fixed Window Limiter</h3>



<p class="wp-block-paragraph">Allows a fixed number of requests within a <strong>fixed time window</strong> (e.g., 100 requests per minute). Once the window resets, the count resets to zero.</p>



<pre class="wp-block-code"><code>options.AddFixedWindowLimiter("fixed", opt =&gt;
{
    opt.PermitLimit = 100;
    opt.Window = TimeSpan.FromMinutes(1);
});</code></pre>



<p class="wp-block-paragraph"><strong>Drawback</strong>: Can allow <strong>bursts at window boundaries</strong> — e.g., 100 requests at 11:59:59 and another 100 at 12:00:00 = 200 requests in 1 second.</p>



<h3 class="wp-block-heading">2. Sliding Window Limiter</h3>



<p class="wp-block-paragraph">Similar to Fixed Window, but divides the window into <strong>smaller segments</strong>, and the limit slides continuously — smoothing out the boundary-burst problem of Fixed Window.</p>



<pre class="wp-block-code"><code>options.AddSlidingWindowLimiter("sliding", opt =&gt;
{
    opt.PermitLimit = 100;
    opt.Window = TimeSpan.FromMinutes(1);
    opt.SegmentsPerWindow = 4; // divides window into 4 segments of 15 sec each
});</code></pre>



<p class="wp-block-paragraph"><strong>Benefit</strong>: More accurate, fairer rate limiting than Fixed Window, at the cost of slightly more overhead/complexity.</p>



<h3 class="wp-block-heading">3. Token Bucket Limiter</h3>



<p class="wp-block-paragraph">Maintains a &#8220;bucket&#8221; of tokens that <strong>refill at a steady rate</strong> up to a maximum capacity. Each request consumes a token; if the bucket is empty, the request is rejected. Allows <strong>controlled bursts</strong> as long as tokens are available.</p>



<pre class="wp-block-code"><code>options.AddTokenBucketLimiter("token", opt =&gt;
{
    opt.TokenLimit = 100;
    opt.TokensPerPeriod = 20;
    opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
    opt.QueueLimit = 0;
});</code></pre>



<p class="wp-block-paragraph"><strong>Best for</strong>: APIs that want to allow occasional bursts of traffic while maintaining a steady average rate over time.</p>



<h3 class="wp-block-heading">4. Concurrency Limiter</h3>



<p class="wp-block-paragraph">Limits the number of <strong>simultaneous/concurrent requests</strong> being processed, rather than counting requests over time. Once a request finishes, a &#8220;slot&#8221; frees up for the next one.</p>



<pre class="wp-block-code"><code>options.AddConcurrencyLimiter("concurrency", opt =&gt;
{
    opt.PermitLimit = 10; // max 10 concurrent requests
    opt.QueueLimit = 5;   // up to 5 requests can queue and wait
});</code></pre>



<p class="wp-block-paragraph"><strong>Best for</strong>: Protecting resource-intensive endpoints (e.g., heavy computation, file processing) where the concern is <strong>simultaneous load</strong>, not total request count over time.</p>



<p><b>83. What is OnRejected ? What is Retry-After ?</b></p>



<p class="wp-block-paragraph"><strong><code>OnRejected</code></strong> is a <strong>callback delegate</strong> in ASP.NET Core&#8217;s Rate Limiting Middleware that gets executed whenever a request is <strong>rejected</strong> because it exceeded the configured rate limit. It lets you customize what happens on rejection — instead of just relying on the default <code>429</code> response, you can add custom logging, headers, or a custom response body.</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why It&#8217;s Useful:</span></p>



<p class="wp-block-paragraph">By default, a rejected request just returns a bare <code>429 Too Many Requests</code> with no body. <code>OnRejected</code> lets you:</p>



<ul class="wp-block-list">
<li>Log rejected requests (for monitoring/alerting on abuse patterns)</li>



<li>Return a custom error message/JSON body</li>



<li>Add a <code>Retry-After</code> header to tell the client when to try again</li>



<li>Track metrics (e.g., increment a counter for rate-limit violations)</li>
</ul>



<p class="wp-block-paragraph"><strong><code>Retry-After</code></strong> is a standard <strong>HTTP response header</strong> (defined in RFC 7231/9110) that tells the client <strong>how long to wait</strong> before making another request. It&#8217;s most commonly used alongside <code>429 Too Many Requests</code> (rate limiting) and <code>503 Service Unavailable</code> (temporary outages/maintenance).</p>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Format:</span></p>



<p class="wp-block-paragraph">It can be specified in <strong>two ways</strong>:</p>



<pre class="wp-block-code"><code>Retry-After: 30</code></pre>



<p class="wp-block-paragraph">→ Wait <strong>30 seconds</strong> before retrying.</p>



<pre class="wp-block-code"><code>Retry-After: Wed, 21 Oct 2026 07:28:00 GMT</code></pre>



<p class="wp-block-paragraph">Retry-After: Wed, 21 Oct 2026 07:28:00 GMT</p>



<p><b>84. Where should rate limiting middleware be placed in the request pipeline ?</b></p>



<p class="wp-block-paragraph">Rate limiting middleware should be placed <strong>early in the pipeline</strong> — after exception handling/HTTPS redirection, but before routing, authentication, and authorization — so excessive requests are rejected cheaply before consuming server resources, and sensitive endpoints like login remain protected from brute-force abuse.</p>



<pre class="wp-block-code"><code>var app = builder.Build();

app.UseExceptionHandler("/error");   // 1. Catch unhandled exceptions first
app.UseHttpsRedirection();            // 2. Enforce HTTPS early

app.UseRateLimiter();                 // 3. Rate limit BEFORE expensive work

app.UseRouting();                     // 4. Routing
app.UseAuthentication();              // 5. AuthN
app.UseAuthorization();               // 6. AuthZ

app.MapControllers()
   .RequireRateLimiting("fixed");     // can also apply per-endpoint

app.Run();</code></pre>



<p class="wp-block-paragraph"><span style="text-decoration: underline;">Why Place It Early?</span></p>



<ol class="wp-block-list">
<li><strong>Reject cheaply, before doing real work</strong> — the whole point of rate limiting is to protect resources. If it runs <em>after</em> authentication, database calls, or business logic, the server has already spent CPU/DB/network resources on a request it&#8217;s about to reject anyway — defeating the purpose.</li>



<li><strong>Protects against unauthenticated abuse too</strong> — if placed after <code>UseAuthentication()</code>, an attacker flooding the login endpoint or public unauthenticated endpoints wouldn&#8217;t be rate-limited until after auth logic runs, which itself can be exploited (e.g., brute-force attacks, credential stuffing) for the very endpoints that most need protecting.</li>



<li><strong>Consistent with .NET&#8217;s benchmark ordering guidance</strong> — Microsoft&#8217;s own recommended middleware order places rate limiting near the top, alongside exception handling and HTTPS redirection, and before routing/auth.</li>
</ol>



<p><b>85. How do you improve ASP.NET Core API performance ?</b></p>



<p class="wp-block-paragraph">This is a broad, common interview question testing whether you know performance optimization <strong>across the full stack</strong> — not just one trick. Structure your answer around key areas:</p>



<h3 class="wp-block-heading">1. Asynchronous Programming</h3>



<p class="wp-block-paragraph">Avoid blocking calls (<code>.Result</code>, <code>.Wait()</code>) that tie up thread pool threads. Use <code>async</code>/<code>await</code> throughout the call chain for I/O-bound operations (DB calls, HTTP calls, file I/O).</p>



<pre class="wp-block-code"><code>// ❌ Blocks a thread pool thread
var data = _repository.GetDataAsync().Result;

// ✅ Frees the thread while waiting
var data = await _repository.GetDataAsync();</code></pre>



<h3 class="wp-block-heading">2. Caching</h3>



<p class="wp-block-paragraph">Reduce repeated expensive work using <code>IMemoryCache</code> (single instance) or <code>IDistributedCache</code>/Redis (multi-instance), plus <strong>Output Caching</strong> for full response caching.</p>



<h3 class="wp-block-heading">3. Efficient Database Access (EF Core)</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Technique</th><th>Why</th></tr></thead><tbody><tr><td><code>AsNoTracking()</code></td><td>Skips change-tracking overhead for read-only queries</td></tr><tr><td>Avoid N+1 queries</td><td>Use <code>.Include()</code> or projections instead of lazy-loading in loops</td></tr><tr><td>Select only needed columns</td><td>Use <code>.Select()</code> projections instead of pulling full entities</td></tr><tr><td>Compiled queries</td><td>Cache query execution plans for hot-path queries</td></tr><tr><td>Connection pooling</td><td>Enabled by default with EF Core&#8217;s <code>DbContext</code> pooling (<code>AddDbContextPool</code>)</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">4. Response Compression</h3>



<p class="wp-block-paragraph">Reduces payload size over the network.</p>



<h3 class="wp-block-heading">5. Minimize Middleware Pipeline Overhead</h3>



<p class="wp-block-paragraph">Keep the middleware pipeline lean — remove unused middleware, order it efficiently (put cheap/rejecting middleware like rate limiting early).</p>



<h3 class="wp-block-heading">6. Use Minimal APIs Where Appropriate</h3>



<p class="wp-block-paragraph">Minimal APIs have less overhead than full MVC controllers for simple endpoints, due to a lighter execution pipeline.</p>



<h3 class="wp-block-heading">8. Pagination &amp; Limiting Payload Size</h3>



<p class="wp-block-paragraph">Never return unbounded result sets — paginate large collections to reduce response size and DB load.</p>



<pre class="wp-block-code"><code>var results = await _context.Products
    .Skip((page - 1) * pageSize)
    .Take(pageSize)
    .ToListAsync();</code></pre>



<h3 class="wp-block-heading">9. HTTP Client Best Practices</h3>



<p class="wp-block-paragraph">Use <code>IHttpClientFactory</code> (avoids socket exhaustion from creating raw <code>HttpClient</code> instances) and configure connection pooling/timeouts properly.</p>



<h3 class="wp-block-heading">10. Rate Limiting &amp; Load Shedding</h3>



<p class="wp-block-paragraph">Prevent overload from excessive/abusive traffic (covered earlier) — protects the API from degrading under unexpected spikes.</p>



<h3 class="wp-block-heading">11. Kestrel &amp; Hosting Configuration</h3>



<ul class="wp-block-list">
<li>Tune <code>Kestrel</code> limits (max concurrent connections, request body size).</li>



<li>Run behind a reverse proxy (e.g., YARP, Nginx) with proper load balancing.</li>



<li>Enable HTTP/2 or HTTP/3 for multiplexed connections where applicable.</li>
</ul>



<h3 class="wp-block-heading">12. Monitoring &amp; Profiling</h3>



<p class="wp-block-paragraph">Use <strong>Application Insights</strong>, <strong><code>dotnet-trace</code></strong>, <strong>MiniProfiler</strong>, or <strong>BenchmarkDotNet</strong> to identify actual bottlenecks — optimize based on data, not guesses.</p>



<p class="wp-block-paragraph">Why Interviewers Ask This:</p>



<ol class="wp-block-list">
<li><strong>Tests breadth of practical knowledge</strong> — performance issues can come from many layers (code, database, network, infrastructure); a strong candidate can reason across all of them.</li>



<li><strong>Real-world signal</strong> — this question separates candidates who&#8217;ve actually debugged production performance issues from those who&#8217;ve only worked on small, low-traffic apps.</li>



<li><strong>Follow-up likely</strong>: <em>&#8220;What tools would you use to actually find the bottleneck before optimizing?&#8221;</em> → <strong>Always measure first</strong> — use profiling/APM tools (Application Insights, <code>dotnet-trace</code>, BenchmarkDotNet) to identify the <em>real</em> bottleneck rather than guessing and optimizing the wrong thing.</li>
</ol>



<p><b>86. Why is asynchronous programming important ?</b></p>



<p class="wp-block-paragraph">Asynchronous programming is important because it allows the server to <strong>handle many concurrent requests efficiently</strong>, without wasting threads sitting idle while waiting on slow I/O operations (database calls, HTTP requests, file access).</p>



<p class="wp-block-paragraph">The Core Problem It Solves:</p>



<p class="wp-block-paragraph">ASP.NET Core uses a <strong>limited thread pool</strong> to handle incoming requests. If a request performs a <strong>blocking</strong> (synchronous) I/O call, the thread handling it sits <strong>idle</strong>, doing nothing but waiting — yet it&#8217;s still &#8220;busy&#8221; from the thread pool&#8217;s perspective, unavailable to serve other incoming requests.</p>



<pre class="wp-block-code"><code>// ❌ Synchronous/blocking: thread is stuck waiting for the DB
public IActionResult GetProduct(int id)
{
    var product = _repository.GetById(id); // blocks the thread
    return Ok(product);
}</code></pre>



<p class="wp-block-paragraph">With <code>async</code>/<code>await</code>, the thread is <strong>released back to the thread pool</strong> while waiting for the I/O operation to complete, and only resumes execution when the result is ready — meaning that same thread can serve <strong>other requests</strong> in the meantime.</p>



<pre class="wp-block-code"><code>// ✅ Asynchronous: thread is freed during the wait
public async Task&lt;IActionResult&gt; GetProduct(int id)
{
    var product = await _repository.GetByIdAsync(id); // thread returns to pool while waiting
    return Ok(product);
}</code></pre>



<p class="wp-block-paragraph">Why This Matters for Scalability:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th></th><th>Synchronous</th><th>Asynchronous</th></tr></thead><tbody><tr><td>Thread during I/O wait</td><td>Blocked, unusable</td><td>Released, reusable</td></tr><tr><td>Requests handled per available thread</td><td>1 at a time (thread tied up)</td><td>Many (thread reused while others wait)</td></tr><tr><td>Behavior under high load</td><td>Thread pool exhaustion → requests queue/timeout</td><td>Scales much better — same threads serve far more concurrent requests</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Under high traffic, <strong>synchronous I/O-bound code can exhaust the thread pool</strong> — all threads get stuck waiting on slow database/network calls, and new incoming requests have no thread available to even start processing, causing timeouts and cascading failures. Asynchronous code avoids this by freeing threads during waits, letting a small number of threads efficiently serve a much larger number of concurrent requests.</p>



<p><b>87. What is the difference between Task, ValueTask, and void ?</b></p>



<p class="wp-block-paragraph"><code>Task</code>, <code>ValueTask</code>, and <code>void</code> are all related to how a method represents its completion, but they serve very different purposes. This is particularly important when writing <strong>asynchronous code in ASP.NET Core</strong>.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th><code>Task</code></th><th><code>ValueTask</code></th><th><code>void</code></th></tr></thead></table></figure>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Represents async operation</td><td>Yes</td><td>Yes</td><td>No</td></tr></tbody></table></figure>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Can be awaited</td><td>Yes</td><td>Yes</td><td>No</td></tr></tbody></table></figure>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Can return a result</td><td><code>Task&lt;T&gt;</code></td><td><code>ValueTask&lt;T&gt;</code></td><td>No</td></tr></tbody></table></figure>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Supports exception propagation through <code>await</code></td><td>Yes</td><td>Yes</td><td>No</td></tr></tbody></table></figure>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Can be used with <code>async</code></td><td>Yes</td><td>Yes</td><td>Yes</td></tr></tbody></table></figure>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Recommended for ASP.NET Core actions</td><td><strong>Yes</strong></td><td>Sometimes</td><td>Generally <strong>No</strong></td></tr></tbody></table></figure>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td>Typical use</td><td>Most async operations</td><td>Performance-sensitive operations that often complete synchronously</td><td>Synchronous methods / event handlers</td></tr></tbody></table></figure>



<h3>1. <code>Task</code></h3>



<p class="wp-block-paragraph"><code>Task</code> represents an <strong>asynchronous operation that will complete in the future</strong>. It is the most common return type for asynchronous methods in ASP.NET Core.</p>



<pre class="wp-block-code"><code>public async Task GetDataAsync()
{
    await Task.Delay(1000);
}</code></pre>



<p class="wp-block-paragraph">When a value needs to be returned, use <code>Task&lt;T&gt;</code>:</p>



<pre class="wp-block-code"><code>public async Task&lt;string&gt; GetNameAsync()
{
    await Task.Delay(1000);
    return "John";
}</code></pre>



<p class="wp-block-paragraph">The caller can use <code>await</code> to asynchronously wait for completion:</p>



<pre class="wp-block-code"><code>string name = await GetNameAsync();</code></pre>



<p class="wp-block-paragraph"><strong>Interview point:</strong> <code>Task</code> is the default choice for most asynchronous methods in ASP.NET Core.</p>



<h3>2. <code>ValueTask</code></h3>



<p class="wp-block-paragraph"><code>ValueTask</code> also represents an asynchronous operation, but it is designed for scenarios where an operation <strong>frequently completes synchronously</strong>.</p>



<pre class="wp-block-code"><code>public ValueTask&lt;string&gt; GetNameAsync()
{
    return ValueTask.FromResult("John");
}</code></pre>



<p class="wp-block-paragraph">The caller can still use <code>await</code>:</p>



<p class="wp-block-paragraph">string name = await GetNameAsync();</p>



<p class="wp-block-paragraph">The main advantage of <code>ValueTask</code> is that it can sometimes avoid allocating a new <code>Task</code> when the result is already available.</p>



<p class="wp-block-paragraph">However, <code>ValueTask</code> is <strong>not automatically better than <code>Task</code></strong>. It has additional usage restrictions and can make code more complicated. Therefore, use it primarily when performance measurements show that avoiding <code>Task</code> allocations is beneficial.</p>



<h3>3. <code>void</code></h3>



<p class="wp-block-paragraph"><code>void</code> means that the method <strong>does not return a value and does not represent an asynchronous operation</strong>.</p>



<pre class="wp-block-code"><code>public void SaveData()
{
    // Synchronous operation
}</code></pre>



<p class="wp-block-paragraph">You can technically declare an <code>async void</code> method:</p>



<pre class="wp-block-code"><code>public async void ProcessDataAsync()
{
    await Task.Delay(1000);
}</code></pre>



<p class="wp-block-paragraph">But <strong><code>async void</code> should generally be avoided</strong> in ASP.NET Core application code.</p>



<p class="wp-block-paragraph">Unlike <code>Task</code>, an <code>async void</code> method cannot be awaited by its caller:</p>



<pre class="wp-block-code"><code>ProcessDataAsync();
// Caller cannot await it</code></pre>



<p class="wp-block-paragraph">This makes it difficult to determine when the operation has finished and makes exception handling more difficult.</p>



<p class="wp-block-paragraph"><code>async void</code> is mainly appropriate for <strong>event handlers</strong>, where the event pattern requires a <code>void</code> return type.</p>



<p><b>88. What causes thread-pool starvation ?</b></p>
<p>Thread-Pool starvation occurs when Thread Pool threads are occupied or blocked for too long, leaving insufficient threads to process incoming work. Common causes include synchronous blocking of asynchronous operations using .Result or .Wait(), Thread.Sleep, synchronous I/O, excessive CPU-bound work, and lock contention. In ASP.NET Core, it can result in increased latency, low throughput, and request timeouts. The primary solution is to use asynchronous, non-blocking APIs and avoid blocking Thread Pool threads.</p>
<p><b>89. How can database queries affect API performance ?</b></p>
<p>Database queries directly affect API performance because database operations are often a significant part of an API request&#8217;s response time. Slow queries, missing indexes, excessive data retrieval, N+1 queries, unnecessary round trips, and large result sets can increase latency and resource consumption. In ASP.NET Core, we can improve performance by using asynchronous database operations, proper indexing, projections, pagination, AsNoTracking() for suitable read-only queries, avoiding N+1 queries, and caching frequently accessed data.</p>



<h3 class="wp-block-heading">How to improve database-related API performance?</h3>



<p class="wp-block-paragraph">Common techniques include:</p>



<ul class="wp-block-list">
<li>Use <strong>indexes</strong> appropriately.</li>



<li>Use <strong>async database operations</strong>.</li>



<li>Select only the required columns.</li>



<li>Use <strong>pagination</strong> for large datasets.</li>



<li>Avoid the <strong>N+1 query problem</strong>.</li>



<li>Reduce unnecessary database round trips.</li>



<li>Use <code>AsNoTracking()</code> for suitable read-only EF Core queries.</li>



<li>Analyze slow queries using <strong>execution plans</strong> and database monitoring.</li>



<li>Use caching where appropriate.</li>



<li>Keep database transactions as short as practical.</li>



<li>Avoid returning unnecessarily large datasets.</li>
</ul>



<p><b>90. How can you reduce unnecessary database calls ?</b></p>
<p>To reduce unnecessary database calls, retrieve only the data required, avoid N+1 queries, batch operations, reuse data within a request, implement caching, use pagination and projections, and use AsNoTracking() for read-only EF Core queries. The goal is to minimize database round trips while ensuring that the queries that remain are efficient and properly indexed.</p>
<p><b>91. What is Response Compression in ASP.NET Core?</b></p>
<p>Response compression in ASP.NET Core is a middleware-based feature that compresses HTTP responses before sending them to clients. It reduces response size, saves bandwidth, and can improve network performance. ASP.NET Core supports compression algorithms such as Brotli and Gzip, and the middleware selects an appropriate encoding based on the client&#8217;s <code>Accept-Encoding</code> header.</p>



<p class="wp-block-paragraph"><b>92. What is Connection Pooling vs Object Pooling</b></p>



<p class="wp-block-paragraph">Connection pooling reuses database connections to avoid the overhead of repeatedly establishing physical connections. Object pooling reuses application objects to reduce object allocation and initialization overhead. Connection pooling is primarily handled by the database provider, while object pooling can be explicitly implemented using mechanisms such as ASP.NET Core&#8217;s <code>ObjectPool&lt;T&gt;</code>.</p>



<p><b>93. How can you optimize large API responses ?</b></p>



<p class="wp-block-paragraph">Large API responses can be optimized by using pagination, filtering, projection, DTOs, response compression, caching, and streaming for very large datasets. You should avoid returning unnecessary fields or deeply nested object graphs and retrieve only the data required by the client. The goal is to reduce database work, serialization overhead, memory consumption, and network bandwidth while keeping the API responsive.</p>



<p><b>94. What is pagination ? Why should APIs use pagination for large datasets ?</b></p>



<p class="wp-block-paragraph"><strong>Pagination</strong> is the technique of dividing a large dataset into smaller, manageable portions called <strong>pages</strong>, instead of returning all records in a single API response.</p>



<p class="wp-block-paragraph">For example, instead of returning 100,000 products at once, an API might return <strong>20 or 50 products per request</strong>.</p>



<pre class="wp-block-code"><code>GET /api/products?page=1&amp;pageSize=20</code></pre>



<p class="wp-block-paragraph">The client can then request the next page:</p>



<pre class="wp-block-code"><code>GET /api/products?page=2&amp;pageSize=20</code></pre>



<h3>Example in ASP.NET Core</h3>



<p class="wp-block-paragraph">With Entity Framework Core:</p>



<pre class="wp-block-code"><code>var products = await db.Products
    .OrderBy(p =&gt; p.Id)
    .Skip((page - 1) * pageSize)
    .Take(pageSize)
    .ToListAsync();</code></pre>



<p class="wp-block-paragraph">Here:</p>



<ul class="wp-block-list">
<li><code>page</code> specifies the requested page.</li>



<li><code>pageSize</code> specifies how many records to return.</li>



<li><code>Skip()</code> ignores records belonging to previous pages.</li>



<li><code>Take()</code> retrieves only the records required for the current page.</li>
</ul>



<h3>Why Should APIs Use Pagination?</h3>



<p class="wp-block-paragraph"><strong>1. Reduces Response Size</strong></p>



<p class="wp-block-paragraph">Instead of sending thousands of records, the API sends only a small subset.</p>



<pre class="wp-block-code"><code>Without pagination:
Database → 100,000 records → API → Client

With pagination:
Database → 50 records → API → Client</code></pre>



<p class="wp-block-paragraph">This reduces network bandwidth and response time.</p>



<p class="wp-block-paragraph"><strong>2. Reduces Memory Usage</strong></p>



<p class="wp-block-paragraph">Loading a huge dataset into application memory can consume significant resources. Pagination keeps the amount of data processed at a time relatively small.</p>



<p class="wp-block-paragraph"><strong>3. Improves Database Performance</strong></p>



<p class="wp-block-paragraph">The database doesn&#8217;t need to return the entire dataset for every request.</p>



<p class="wp-block-paragraph"><strong>4. Improves API Response Time</strong></p>



<p class="wp-block-paragraph">Smaller responses generally mean faster database processing, serialization, network transfer, and client-side processing.</p>



<p class="wp-block-paragraph"><strong>5. Improves User Experience</strong></p>



<p class="wp-block-paragraph">Clients can display the first page immediately rather than waiting for a massive response.</p>



<p class="wp-block-paragraph"><strong>6. Prevents Resource Exhaustion</strong></p>



<p class="wp-block-paragraph">Without pagination, a poorly designed API could allow a request to retrieve millions of records, potentially consuming excessive <strong>CPU, memory, database connections, and network bandwidth</strong>.</p>



<h3>Offset vs. Cursor Pagination</h3>



<p class="wp-block-paragraph">For simple APIs, <strong>offset pagination</strong> is common:</p>



<pre class="wp-block-code"><code>GET /api/products?page=5&amp;pageSize=50</code></pre>



<p class="wp-block-paragraph">For very large or frequently changing datasets, <strong>cursor/keyset pagination</strong> can be more efficient because it avoids increasingly expensive large offsets.</p>



<p class="wp-block-paragraph">For example:</p>



<pre class="wp-block-code"><code>GET /api/products?afterId=500&amp;pageSize=50</code></pre>



<h3 class="wp-block-heading">Interview Answer</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>Pagination is the process of dividing a large dataset into smaller pages and returning only a limited number of records per API request. APIs should use pagination for large datasets because it reduces database workload, response size, network bandwidth, serialization time, and memory consumption. It also improves response time, scalability, and user experience. For very large datasets, cursor or keyset pagination can be preferable to traditional offset-based pagination.</strong></p>
</blockquote>



<p class="wp-block-paragraph"><strong>In short: Pagination prevents an API from trying to return the entire database in a single response.</strong></p>



<p><b>95. What is the difference between BackgroundService and IHostedService?</b></p>



<p class="wp-block-paragraph">Both <strong><code>IHostedService</code></strong> and <strong><code>BackgroundService</code></strong> are used to run background tasks in ASP.NET Core applications. The key difference is that <strong><code>IHostedService</code> is an interface</strong>, while <strong><code>BackgroundService</code> is an abstract base class that implements <code>IHostedService</code></strong> and provides a convenient structure for long-running background work.</p>



<h3><code>IHostedService</code></h3>



<p class="wp-block-paragraph"><code>IHostedService</code> defines two methods:</p>



<pre class="wp-block-code"><code>public interface IHostedService
{
    Task StartAsync(CancellationToken cancellationToken);
    Task StopAsync(CancellationToken cancellationToken);
}</code></pre>



<p class="wp-block-paragraph">You implement these methods yourself:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class MyHostedService : IHostedService
{
    public Task StartAsync(CancellationToken cancellationToken)
    {
        // Start background work
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        // Cleanup
        return Task.CompletedTask;
    }
}
</pre></div>


<p class="wp-block-paragraph">It gives you <strong>complete control</strong> over how the service starts, runs, and stops.</p>



<h3><code>BackgroundService</code></h3>



<p class="wp-block-paragraph"><code>BackgroundService</code> is an abstract class that implements <code>IHostedService</code> and provides an <code>ExecuteAsync()</code> method specifically designed for long-running background operations.</p>



<pre class="wp-block-code"><code>public class MyBackgroundService : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Do background work

            await Task.Delay(
                TimeSpan.FromSeconds(10),
                stoppingToken);
        }
    }
}</code></pre>



<p class="wp-block-paragraph">Register it with dependency injection:</p>



<pre class="wp-block-code"><code>builder.Services.AddHostedService&lt;MyBackgroundService&gt;();</code></pre>



<h3>Key Differences</h3>



<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary"><th>Feature</th><th><code>IHostedService</code></th><th><code>BackgroundService</code></th></tr></thead><tbody><tr><td>Type</td><td>Interface</td><td>Abstract base class</td></tr><tr><td>Main methods</td><td><code>StartAsync()</code> / <code>StopAsync()</code></td><td><code>ExecuteAsync()</code></td></tr><tr><td>Long-running tasks</td><td>You implement the pattern yourself</td><td>Built-in pattern</td></tr><tr><td>Control</td><td>More control</td><td>More convenient</td></tr><tr><td>Typical use</td><td>Custom lifecycle/start-stop logic</td><td>Continuous background processing</td></tr></tbody></table></div>



<h3>When should you use each?</h3>



<p class="wp-block-paragraph">Use <strong><code>BackgroundService</code></strong> when you need a continuously running worker, such as:</p>



<ul class="wp-block-list">
<li>Processing messages from a queue</li>



<li>Sending scheduled notifications</li>



<li>Consuming events</li>



<li>Periodically processing data</li>



<li>Running background cleanup jobs</li>
</ul>



<p class="wp-block-paragraph">Use <strong><code>IHostedService</code></strong> when you need more direct control over the <strong>application startup and shutdown lifecycle</strong>, or when your background task doesn&#8217;t fit the <code>ExecuteAsync()</code> pattern.</p>



<h2>Scenario-Based Interview Questions</h2>
<p>Scenario-Based Interview Questions in ASP.NET Core interviews are designed to test how well you can apply your knowledge to real-world development and production problems, rather than simply recall definitions. Instead of asking “What is dependency injection?”, an interviewer may ask, “Your API is becoming slow as traffic increases. How would you identify and fix the performance bottleneck?” You may be given scenarios involving API performance, database optimization, caching, authentication and authorization, dependency injection, middleware, exception handling, concurrency, background services, logging, scalability, and security. These questions evaluate your ability to analyze a problem, identify its root cause, choose an appropriate ASP.NET Core feature or architecture, and explain why your solution is suitable.</p>



<p><b>96. Scenario 1 — Slow API</b> &#8211; Your API takes 5 seconds to respond. CPU usage is low, but database usage is high. How would you investigate and fix it?</p>
<p>Your answer should include the areas:</p>



<pre class="wp-block-code"><code>SQL query analysis
EF Core generated SQL
indexes
AsNoTracking()
projection
pagination
N+1 queries
database profiling</code></pre>



<p><b>97. Scenario 2 — High CPU</b> &#8211; An API suddenly reaches 90% CPU because one user is sending thousands of requests. What would you do?</p>
<p>Expected answer:</p>



<pre class="wp-block-code"><code>rate limiting
authentication
per-user/IP partitioning
caching
monitoring
potentially blocking abusive clients</code></pre>



<p><b>98. Scenario 3: Singleton Problem</b> &#8211; A Singleton service depends on DbContext. Is this safe?</p>
<p>No. DbContext is normally scoped, and injecting a scoped service into a singleton creates a lifetime mismatch.</p>
<p><b>99. Scenario 4 : Authentication Works but Authorization Fails</b> &#8211; A user has a valid JWT but receives 403 Forbidden. What would you investigate?</p>
<p>Check:</p>



<pre class="wp-block-code"><code>authentication succeeded
required role/claim
authorization policy
claim type
issuer
audience
middleware order</code></pre>



<p><b>100. Scenario 5 : Duplicate Database Records</b> &#8211; Two requests arrive simultaneously and create the same record. How would you prevent duplicates?</p>
<p>Possible solutions:</p>



<pre class="wp-block-code"><code>database unique constraint
transaction
concurrency handling
idempotency keys
appropriate application-level locking where necessary
Top 20 Questions to Prepare First</code></pre>



<div class="note">Conclusion</div>



<p class="wp-block-paragraph">These ASP.NET Core interview questions cover the most important concepts, practical scenarios, and real-world problems that you are highly likely to encounter in an ASP.NET Core interview. Make sure you understand not just the answers, but also the reasoning behind them, because interviewers often ask follow-up and scenario-based questions to test your practical knowledge. <strong>Don&#8217;t just read these questions—practice answering them aloud, revisit the topics you find difficult, and make sure you can explain each concept confidently with real-world examples.</strong> If you can master these questions and understand the concepts behind them, you will be much better prepared to face your ASP.NET Core interview with confidence. <strong>Now it&#8217;s your turn: start revising, keep practicing, and walk into your interview fully prepared to succeed. My very best wishes for your ASP.NET Core interview—go confidently, give it your best, and I hope you qualify with flying colors!</strong></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-interview-questions/">ASP.NET Core Interview Questions and Answers – Crack Your Next Interview</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-interview-questions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Effortless ASP.NET Core Minimal API Testing with the Visual Studio HTTP Editor</title>
		<link>https://www.yogihosting.com/aspnet-core-http-editor/</link>
					<comments>https://www.yogihosting.com/aspnet-core-http-editor/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Mon, 07 Sep 2026 04:52:17 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=23482</guid>

					<description><![CDATA[<p>The Visual Studio HTTP File Editor is a powerful built-in tool for testing ASP.NET Core Web APIs and Minimal APIs directly from the IDE. Instead of relying on external tools like Postman, you can create, edit, and execute HTTP requests using .http files, making API development and debugging faster and more efficient. With the Visual [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-http-editor/">Effortless ASP.NET Core Minimal API Testing with the Visual Studio HTTP Editor</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>The Visual Studio <b>HTTP File Editor</b> is a powerful built-in tool for testing ASP.NET Core Web APIs and Minimal APIs directly from the IDE. Instead of relying on external tools like Postman, you can create, edit, and execute HTTP requests using <b>.http</b> files, making API development and debugging faster and more efficient.</p>
<p>With the Visual Studio 2022 HTTP File Editor, you can:</p>
<ul>
<li>Create and manage .http files for organizing and storing API requests.</li>
<li>Send HTTP requests (GET, POST, PUT, DELETE, PATCH, and more) directly from Visual Studio.</li>
<li>View and inspect HTTP responses, including status codes, headers, and response bodies, without leaving the editor.</li>
</ul>



<span id="more-23482"></span>



<p class="wp-block-paragraph">This built-in feature streamlines <b>Web API testing</b>, improves developer productivity, and provides a seamless workflow for developing, debugging, and validating ASP.NET Core Web APIs within Visual Studio.</p>



<h2 class="wp-block-heading">Create a .http file</h2>



<p class="wp-block-paragraph">Follow the steps to create a .http file in Visual Studio.</p>



<ul class="wp-block-list">
<li>In Solution Explorer, right-click the project name.</li>



<li>In the context menu, select Add &gt; New Item.</li>



<li>In the Add New Item dialog, put <b>http</b> in the search box. This will show the http file.</li>



<li>Select HTTP File, and select Add.</li>
</ul>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/create-http-file.png" alt="Create http File" title="Create http File" class="img-fluid"></p>



<p class="wp-block-paragraph">By default the .http is empty. We will add test codes in just a moment. But before that we need to see our Web API.</p>



<div class="note">ASP.NET Core Minimal API</div>



<p class="wp-block-paragraph">We have our Minimal API code which performs CRUD operations on a Work class.</p>



<p class="wp-block-paragraph">The following code defines a set of ASP.NET Core Minimal API endpoints for performing CRUD (Create, Read, Update, and Delete) operations on a Work resource. The <span class="code">MapPost()</span> endpoint creates a new work item and returns a 201 Created response with the resource location. The <span class="code">MapGet()</span> endpoints retrieve all work items, fetch only completed items, or return a specific work item by its ID.</p>



<p class="wp-block-paragraph">The <span class="code">MapPut()</span> endpoint updates an existing work item after verifying that it exists. The <span class="code">MapPatch()</span> endpoint updates an existing work by HTTP Patch method. In the last their is <span class="code">MapDelete()</span> endpoint which removes a work item from the database. Each endpoint uses Entity Framework Core through the WorkDb context to interact with the database asynchronously, ensuring efficient and responsive API operations.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapPost(&quot;/works&quot;, async (Work work, WorkDb db) =&gt;
{
    db.Works.Add(work);
    await db.SaveChangesAsync();

    return Results.Created($&quot;/works/{work.Id}&quot;, work);
});

app.MapGet(&quot;/works&quot;, async (WorkDb db) =&gt;
    await db.Works.ToListAsync());

app.MapGet(&quot;/works/{id}&quot;, async (int id, WorkDb db) =&gt;
    await db.Works.FindAsync(id)
        is Work work
            ? Results.Ok(work)
            : Results.NotFound());

app.MapPut(&quot;/works/{id}&quot;, async (int id, Work work, WorkDb db) =&gt;
{
    var todo = await db.Works.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = work.Name;
    todo.TimeStart = work.TimeStart;
    todo.TimeEnd = work.TimeEnd;
    todo.IsComplete = work.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapPatch(&quot;/works/{id}&quot;, async (int id, WorkDto workDto, WorkDb db) =&gt;
{
    var todo = await db.Works.FindAsync(id);

    if (todo is null) return Results.NotFound();

    if (workDto.Name is not null) todo.Name = workDto.Name;
    if (workDto.IsComplete is not null) todo.IsComplete = workDto.IsComplete.Value;
    if (workDto.TimeStart is not null) todo.TimeStart = workDto.TimeStart;
    if (workDto.TimeEnd is not null) todo.TimeEnd = workDto.TimeEnd;

    await db.SaveChangesAsync();

    return Results.NoContent();
});

app.MapDelete(&quot;/works/{id}&quot;, async (int id, WorkDb db) =&gt;
{
    if (await db.Works.FindAsync(id) is Work work)
    {
        db.Works.Remove(work);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});
</pre></div>


<p class="wp-block-paragraph">The work class is given below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class Work
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string TimeStart { get; set; }
    public string TimeEnd { get; set; }
    public bool IsComplete { get; set; }
}
</pre></div>


<p class="wp-block-paragraph">The WorkDto.cs is:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class WorkDto
{
    public string Name { get; set; }

    public string TimeStart { get; set; }

    public string TimeEnd { get; set; }

    public bool? IsComplete { get; set; }
}
</pre></div>


<p>Now we are going to test this Web API with .http file.</p>
<h2>Endpoints Explorer in Visual Studio</h2>
<p>The <b>Endpoints Explorer</b> is a Visual Studio tool window that displays all the endpoints available in your ASP.NET Core Web API. It provides a convenient way to view your API routes and quickly test them by generating or using a .http file, allowing you to send HTTP requests directly from within Visual Studio.</p>
<h3>Open Endpoints Explorer</h3>
<p>Select <span class="term">View &gt; Other Windows &gt; Endpoints Explorer</span>. This will open Endpoints Explorer.</p>
<p>The Endpoints Explorer will show all the Web API Endpoints defined in the app. If you don&#8217;t see the endpoints then click the <u>Refresh</u> button. See below image:</p>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/endpoints-explorer.png" alt="Endpoints Explorer" title="Endpoints Explorer" class="img-fluid"></p>



<div class="note">Add a request to an .http file</div>
<p>We can now generate request to the .http file. For this, in the Endpoints Explorer, right click on the <span class="term">GET Endpoint on /Work</span> and select <span class="term">Generate Request</span>.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/generate-request-endpoints-explorer.png" alt="Generate Request Endpoints Explorer" title="Generate Request Endpoints Explorer" class="img-fluid"></p>
<p>The request is added to that <span class="code">.http</span> file as shown below:</p>



<pre class="wp-block-code"><code>@WorkApi_HostAddress = https://localhost:7026

GET {{WorkApi_HostAddress}}/works

###</code></pre>



<p>Here&#8217;s what each part does:</p>



<pre class="wp-block-code"><code>@WorkApi_HostAddress = https://localhost:7026</code></pre>



<ul>
<li>This defines a variable named <span class="code">WorkApi_HostAddress</span>.</li>
<li>The <span class="code">@</span> prefix marks it as a variable declaration.</li>
<li>Its value is <span class="code">https://localhost:7026</span> — likely the base URL of a locally running Web API project (the port <span class="code">7026</span> is typical of an ASP.NET Core project running over HTTPS during development).</li>
</ul>



<pre class="wp-block-code"><code>GET {{WorkApi_HostAddress}}/works</code></pre>



<ul>
<li>This is the actual HTTP request.</li>
<li><span class="code">GET</span> is the HTTP method.</li>
<li><span class="code">{{WorkApi_HostAddress}}</span> inserts the value of the variable defined above using double-curly-brace syntax.</li>
<li>So this line effectively resolves to:</li>
</ul>



<pre class="wp-block-code"><code>GET https://localhost:7026/works</code></pre>



<p>It sends a GET request to the <span class="code">/works</span> endpoint — presumably a controller/route in the API that returns a list of &#8220;works&#8221; (e.g., work items, projects, or records, depending on the app&#8217;s domain).</p>



<pre class="wp-block-code"><code>http
###</code></pre>



<ul>
<li>This is a <b>request separator</b>.</li>
<li><span class="code">.http</span> files can contain multiple requests in one file, and <span class="code">###</span> marks the boundary between them, so tools know where one request ends and the next begins.</li>
<li>Since there&#8217;s nothing after it here, it just marks the end of this single request block (and would let you add another request below it if needed).</li>
</ul>
<p>If you click &#8220;Send Request&#8221; (you&#8217;d see a clickable link above the GET line) it will:</p>
<ul>
<li>Substitute the variable.</li>
<li>Fire off an HTTPS GET request to <span class="term">https://localhost:7026/works</span>.</li>
<li>Show you the response (status code, headers, body) in the <b>Response pane</b> given on the side.</li>
</ul>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/send-request-http-file.png" alt="Send Request http File" title="Send Request http File" class="img-fluid"></p>
<p>Note that you have to run the project before you make any API request. Also since there are no work records in the database so you will see an empty response. Lets start by posting data with .http file editor.</p>
<h2>Test POST Endpoint with .http File Editor</h2>
<p>The Minimal API Post Endpoint create a <u>Work</u> record and it&#8217;s code is given below:</p>



<pre class="wp-block-code"><code>app.MapPost("/works", async (Work work, WorkDb db) =&gt;
{
    db.Works.Add(work);
    await db.SaveChangesAsync();

    return Results.Created($"/works/{work.Id}", work);
});</code></pre>



<p>Lets test it.</p>
<p>In the Endpoints Explorer, right click on the <span class="term">POST Endpoint on /Work</span> and select <span class="term">Generate Request</span>.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/generate-post-request-http-file-editor.png" alt="Generate Post Request http File Editor" title="Generate Post Request http File Editor" class="img-fluid"></p>
<p>POST request code lines will be added to the <span class="code">.http</span> file as shown below:</p>



<pre class="wp-block-code"><code>POST {{WorkApi_HostAddress}}/works

###</code></pre>



<p>A POST request requires both headers and a request body. To define these components, add the following lines immediately after the POST request line:</p>



<pre class="wp-block-code"><code>Content-Type: application/json

{
  "name":"eat breakfast",
  "isComplete":true,
  "timeStart":"8:00:00",
  "timeEnd":"8:30:00"
}</code></pre>



<p>The preceding code adds a <span class="term">Content-Type</span> header and a <span class="term">JSON request body</span>. The file contents now look as:</p>



<pre class="wp-block-code"><code>@WorkApi_HostAddress = https://localhost:7026

POST {{WorkApi_HostAddress}}/works
Content-Type: application/json

{
  "name":"eat breakfast",
  "isComplete":true,
  "timeStart":"8:00:00",
  "timeEnd":"8:30:00"
}
###</code></pre>



<p>Click the <b>Send request</b> link that is above the POST request line. The POST request is sent to the API and the response is displayed in the Response pane.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/post-request-endpoints-explorer.png" alt="Post Request Endpoints Explorer" title="Post Request Endpoints Explorer" class="img-fluid"></p>
<p>You will see <b>201 Created</b> response along with the json of the newly created work record.</p>



<pre class="wp-block-code"><code>{
  "id": 1,
  "name": "eat breakfast",
  "timeStart": "8:00:00",
  "timeEnd": "8:30:00",
  "isComplete": true
}</code></pre>



<p>Click on the <b>Headers</b> link to see the Request Headers.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/request-headers.png" alt="Request Headers" title="Request Headers" class="img-fluid"></p>
<p>Also click on the <b>Raw</b> link to see the full Request and Response made to the API.</p>
<p><img src=https://www.yogihosting.com/wp-content/uploads/2026/08/endpoints-explorer-raw-description.png"" alt="Endpoints Explorer Raw Description" title="Endpoints Explorer Raw Description" class="img-fluid"></p>
<h2>Test GET Endpoints with .http File Editor</h2>
<p>The Minimal API has 2 GET Endpoints as defined below. The first one gets all the <u>Work</u> records while the second get a particular <u>Work</u> record by it&#8217;s ID.</p>



<pre class="wp-block-code"><code>app.MapGet("/works", async (WorkDb db) =>
    await db.Works.ToListAsync());

app.MapGet("/works/{id}", async (int id, WorkDb db) =>
    await db.Works.FindAsync(id)
        is Work work
            ? Results.Ok(work)
            : Results.NotFound());</code></pre>



<p>In Endpoints Explorer, right-click the first <b>GET endpoint</b>, and select Generate request. This is the same thing we did earlier for the POST endpoint to.</p>
<p>The following content is added to the .http file:</p>



<pre class="wp-block-code"><code>GET {{WorkApi_HostAddress}}/works

###</code></pre>



<p>Now, select the <u>Send request</u> link that is given above the new GET request line.</p>
<p>The GET request is sent to the Minimal Web API and the response is displayed in the Response pane.</p>
<p>The response body is similar to the following JSON:</p>



<pre class="wp-block-code"><code>&#91;
  {
    "id": 1,
    "name": "eat breakfast",
    "timeStart": "8:00:00",
    "timeEnd": "8:30:00",
    "isComplete": true
  }
]</code></pre>



<p>Also check the below image where we have shown this:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/get-request-endpoints-explorer.png" alt="Get Request Endpoints Explorer" title="Get Request Endpoints Explorer" class="img-fluid"></p>
<p>Next, in Endpoints Explorer, right-click the <span class="code">/works/{id}</span> GET endpoint and select Generate request. The following content is added to the <span class="term">.http</span> file:</p>



<pre class="wp-block-code"><code>@id=0
GET {{WorkApi_HostAddress}}/works/{{id}}</code></pre>



<p>It now becomes:</p>



<pre class="wp-block-code"><code>@id=1
GET {{WorkApi_HostAddress}}/works/{{id}}</code></pre>



<ul>
<li>Replace <span class="code">@id=0</span> with <span class="code">@id=1</span> since this is the Id of the 1st Work record whose value we have to fetch.</li>
<li>Select the Send request link that is above the new GET request line.</li>
<li>The GET request is sent to the app and the response is displayed in the Response pane.</li>
</ul>
<p>The response body is similar to the following JSON:</p>



<pre class="wp-block-code"><code>{
  "id": 1,
  "name": "eat breakfast",
  "timeStart": "8:00:00",
  "timeEnd": "8:30:00",
  "isComplete": true
}</code></pre>



<p>We have shown this in the below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/get-request-by-id-endpoints-explorer.png" alt="Get Request by ID Endpoints Explorer" title="Get Request by ID Endpoints Explorer" class="img-fluid"></p>
<h2>Test PUT Endpoint with .http File Editor</h2>
<p>The Minimal Web API&#8217;s PUT Endpoint updates a given Work record and is given below:</p>



<pre class="wp-block-code"><code>app.MapPut("/works/{id}", async (int id, Work work, WorkDb db) =>
{
    var todo = await db.Works.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = work.Name;
    todo.TimeStart = work.TimeStart;
    todo.TimeEnd = work.TimeEnd;
    todo.IsComplete = work.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
});</code></pre>



<p>Before making a PUT request, make sure the item already exists in the database. If necessary, first send a POST request to create the item, and then use the PUT request to update it.</p>
<p>In Endpoints Explorer, right-click the PUT endpoint and select Generate request. Visual Studio adds the following content to the .http file:</p>



<pre class="wp-block-code"><code>PUT {{WorkApi_HostAddress}}/works/{{id}}
Content-Type: application/json

{
  //Work
}

###</code></pre>



<p>We need to update it so in the PUT request line, replace {id} with 1. This is the Id of the Work records that needs to be updated. Then add the following lines containing the json of the Work record in side the curly brackets:</p>



<pre class="wp-block-code"><code>"id": 1,
"name":"eat supper",
"timeStart":"9:00:00",
"timeEnd":"9:30:00",
"isComplete":false</code></pre>



<p>It now becomes:</p>



<pre class="wp-block-code"><code>PUT {{WorkApi_HostAddress}}/works/1
Content-Type: application/json

{
  "id": 1,
  "name":"eat supper",
  "timeStart":"9:00:00",
  "timeEnd":"9:30:00",
  "isComplete":false
}

###</code></pre>



<p>We added a Content-Type header and a JSON request body of the Work that is to be updated. Notice I have changed the values of name to &#8220;eat supper&#8221;, IsComplete to &#8220;false&#8221; and also the times.</p>
<p>Now, select the <b>Send request</b> link above the new PUT request to send the request.</p>
<p>The PUT request is sent to the application, and the response appears in the Response pane. Because the operation completes successfully without returning content, the response body is empty and the HTTP status code is <span class="code">204 No Content</span>.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/put-request-endpoints-explorer.png" alt="Put Request Endpoints Explorer" title="Put Request Endpoints Explorer" class="img-fluid"></p>
<p>Check the record is updated by making a GET request to the API again.</p>
<p>Suppose If we try to update a records which is not present in the database then we will get <b>404 Not Found</b> response. Change the id value to 50, the code now becomes:</p>



<pre class="wp-block-code"><code>PUT {{WorkApi_HostAddress}}/works/50
Content-Type: application/json

{
  "id": 1,
  "name":"eat supper",
  "timeStart":"9:00:00",
  "timeEnd":"9:30:00",
  "isComplete":false
}

###</code></pre>



<p>Since 50 th Work records is not present so we will get <b>404 Not Found</b> response, check below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/404-Not-Found-Endpoints-Explorer.png" alt="404 Not-Found Endpoints Explorer" title="404 Not-Found Endpoints Explorer" class="img-fluid"></p>
<h2>Test PATCH Endpoints with .http File Editor</h2>
<p>The Minimal Web API&#8217;s PATCH Endpoint updates a given Work record by HTTP PATCH method:</p>



<pre class="wp-block-code"><code>app.MapPatch("/works/{id}", async (int id, WorkDto workDto, WorkDb db) =>
{
    var todo = await db.Works.FindAsync(id);

    if (todo is null) return Results.NotFound();

    if (workDto.Name is not null) todo.Name = workDto.Name;
    if (workDto.IsComplete is not null) todo.IsComplete = workDto.IsComplete.Value;
    if (workDto.TimeStart is not null) todo.TimeStart = workDto.TimeStart;
    if (workDto.TimeEnd is not null) todo.TimeEnd = workDto.TimeEnd;

    await db.SaveChangesAsync();

    return Results.NoContent();
});</code></pre>



<p>Lets test it. On the Endpoints Explorer, right-click the PATCH endpoint, and select Generate request. This adds the following code to the http file:</p>



<pre class="wp-block-code"><code>PATCH {{WorkApi_HostAddress}}/works/{id}

###</code></pre>



<p>In the PATCH request line, replace {id} with 1 since we will update the 1st records. Next, add the following code lines after it.</p>



<pre class="wp-block-code"><code>Content-Type: application/json

{
  "name": "sleep",
  "isComplete":false
}</code></pre>



<p>The above code adds a <b>Content-Type</b> header and a JSON request body with only 2 fields to update. These are:</p>
<ul>
<li>name</li>
<li>isComplete</li>
</ul>
<p>The full Patch code becomes.</p>




<pre class="wp-block-code"><code>PATCH {{WorkApi_HostAddress}}/works/{id}
Content-Type: application/json

{
  "name": "sleep",
  "isComplete":false
}

###</code></pre>



<p>Select the Send request link that is above the new PATCH request line.</p>
<p>The <u>PATCH</u> request is sent to the application, and the response appears in the Response pane. The response body is empty, and the HTTP status code is <b>204 No Content</b>, indicating that the update was completed successfully without returning any content.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/patch-request-endpoints-explorer.png" alt="Patch Request Endpoints Explorer" title="Patch Request Endpoints Explorer" class="img-fluid"></p>
<p>Make a new GET request to the API to confirm the change has been made to the Work.</p>
<h2>Test DELETE Endpoints with .http File Editor</h2>
<p>The Minimal API Delete endpoint is:</p>



<pre class="wp-block-code"><code>app.MapDelete("/works/{id}", async (int id, WorkDb db) =>
{
    if (await db.Works.FindAsync(id) is Work work)
    {
        db.Works.Remove(work);
        await db.SaveChangesAsync();
        return Results.NoContent();
    }

    return Results.NotFound();
});</code></pre>



<p>Lets test this endpoint. Just like what we did before, start by right-clicking the DELETE endpoint in the Endpoints Explorer, and select Generate request.</p>
<p>A DELETE request is added to http file.</p>
<p>Replace {id} in the DELETE request line with 1. It will now look as:</p>



<pre class="wp-block-code"><code>DELETE {{WorkApi_HostAddress}}/works/1

###</code></pre>



<p>Select the Send request link for the DELETE request. Visual Studio sends the request to the application, and the response appears in the Response pane. The response body is empty, and the HTTP status code is <b>204 No Content</b>, indicating that the resource was successfully deleted.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/delete-request-endpoints-explorer.png" alt="Delete Request Endpoints Explorer" title="Delete Request Endpoints Explorer" class="img-fluid"></p>
<p>Make a GET request to confirm the Work is indeed deleted.</p>
<h2>Test File Upload Endpoints with .http File Editor</h2>
<p>The Minimal API has an endpoint to upload files. Check the below code:</p>



<pre class="wp-block-code"><code>app.MapPost("/upload", async (IFormFile file) =>
{
    if (file == null || file.Length == 0)
        return Results.BadRequest("No file uploaded.");

    var filePath = Path.Combine("Uploads", file.FileName);

    Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);

    using var stream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(stream);

    return Results.Ok(new
    {
        file.FileName,
        file.Length
    });
}).DisableAntiforgery();</code></pre>



<p>The API code above accepts the uploaded file as an IFormFile parameter and saves it to the Uploads directory.</p>
<p>Notice that we have disabled anti-forgery token by adding <span class="code">DisableAntiforgery()</span>. This is only done for testing it using a .http file. Otherwise we will get the following error:</p>



<pre class="wp-block-code"><code>Invalid anti-forgery token found when reading parameter "IFormFile file"</code></pre>



<p>Right-clicking the <span class="term">POST /upload</span> endpoint in the Endpoints Explorer, and select Generate request.</p>
<p>Update the code as shown below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
POST {{WorkApi_HostAddress}}/upload
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;sample.txt&quot;
Content-Type: text/plain

&lt; ./sample.txt
------WebKitFormBoundary7MA4YWxkTrZu0gW--
</pre></div>


<div class="note">Code Explanation:</div>
<p>The above code uploads <span class="term">sample.txt</span> file to the /upload endpoint as a multipart/form-data request.</p>



<pre class="wp-block-code"><code>Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW</code></pre>



<p><span class="code">multipart/form-data</span> tells the server that the request contains form data that may include files.</p>
<p>The boundary separates the individual parts of the request. In this example, the boundary is:</p>



<pre class="wp-block-code"><code>----WebKitFormBoundary7MA4YWxkTrZu0gW</code></pre>



<p>The same boundary must be used throughout the request.</p>



<pre class="wp-block-code"><code>------WebKitFormBoundary7MA4YWxkTrZu0gW</code></pre>



<p>This marks the beginning of a part in the multipart request.</p>
<p>In this case, there is one part: the uploaded file.</p>



<pre class="wp-block-code"><code>Content-Disposition: form-data; name="file"; filename="sample.txt"</code></pre>



<p>This line provides information about the form field and the file.</p>
<ul>
<li>form-data — indicates that this is a form-data field.</li>
<li>name=&#8221;file&#8221; — must match the parameter name in your Minimal API.</li>
</ul>
<p><img decoding="async" src="" alt="" title="" class="img-fluid"></p>



<pre class="wp-block-code"><code>IFormFile file</code></pre>



<p>filename=&#8221;sample.txt&#8221; — specifies the name of the uploaded file.</p>
<p>The name=&#8221;file&#8221; part is particularly important because ASP.NET Core uses it to bind the uploaded file to.</p>



<pre class="wp-block-code"><code>Content-Type: text/plain</code></pre>



<p>This tells the server that sample.txt contains plain-text data.</p>
<p>For example, if you were uploading a PDF, you could use:</p>



<pre class="wp-block-code"><code>Content-Type: application/pdf</code></pre>



<p>Read the file:</p>



<pre class="wp-block-code"><code>&lt; ./sample.txt	</code></pre>



<p>This is an important feature of the Visual Studio .http file editor.</p>
<p>The &#8220;&lt;&#8221; operator tells Visual Studio to read the contents of <u>sample.txt</u> from the local filesystem and use them as the body of this multipart section.</p>
<p>End of the multipart request:</p>



<pre class="wp-block-code"><code>------WebKitFormBoundary7MA4YWxkTrZu0gW--</code></pre>



<p>The final &#8212; after the boundary indicates that this is the end of the multipart request.</p>
<p>Before you click the Send request make an &#8220;Uploads&#8221; directory in the app root folder. Finally, select the Send request link to upload the file.</p>
<p>The below image shows the response.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/file-upload-request-endpoints-explorer.png" alt="File Upload Request Endpoints Explorer" title="File Upload Request Endpoints Explorer" class="img-fluid"></p>
<p>Open the &#8220;Uploads&#8221; folder to find the &#8220;sample.txt&#8221; file uploaded successfully. The file content will be <span class="term">< ./sample.txt</span>. See below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/file-upload-http-file-visual-studio.png" alt="File Upload http file Visual Studio" title="File Upload http file Visual Studio" class="img-fluid"></p>
<h2>Understanding .http file in Details</h2>
<p>We will now go through details of .http file so that you can understand how to write request based on different conditions.</p>
<h3>Request headers</h3>
<p>To add one or more headers, place each header on its own line directly after the request line, with no blank lines separating the request line from the first header or between the headers themselves. Each header follows the format <span class="code">HeaderName: Value</span>, as shown in the example below:</p>



<pre class="wp-block-code"><code>GET https://localhost:8778/weatherforecast
Cache-Control: max-age=857800
Age: 150

###</code></pre>



<p>Here we have 2 headers: <span class="term">Cache-Control</span> with value <span class="term">max-age=857800</span>. And <span class="term">Age</span> with value <span class="term">150</span>.</p>
<h3>Request body</h3>
<p>To include a body with a request (typically for POST, PUT, or PATCH), leave exactly one blank line after the last header, then write the body content starting on the next line — this blank line is required to separate headers from the body. For a JSON body, set <span class="code">Content-Type: application/json</span> as a header and write the JSON directly below:</p>




<pre class="wp-block-code"><code># creating a new work by making a post request to the API
POST https://localhost:8778/api/work
Content-Type: application/json

{
  "title": "Buy groceries",
  "isComplete": false
}</code></pre>



<p>Note that lines that start with either # or // are comments.</p>
<h3>Variables</h3>
<p>In a Visual Studio <span class="code">.http</span> file, you define a variable on a line that starts with <span class="code">@</span>, using the syntax <span class="code">@VariableName=Value</span>. Variable names are case-sensitive and can&#8217;t contain spaces. The value can be any characters, including <span class="code">null</span> to represent a null value.</p>
<p>Once defined, a variable can be referenced in any request that appears later in the file. To reference it, wrap the variable name in double curly braces: <span class="code">{{</span> and <span class="code">}}</span>. The example below shows two variables being defined and then used in a request:</p>



<pre class="wp-block-code"><code>@hostname=localhost
@port=99887
GET https://{{hostname}}:{{port}}/works</code></pre>



<p>Variables can also be defined in terms of other variables, as long as those variables were defined earlier in the file. The example below uses a single combined variable in the request, instead of the two separate variables shown in the previous example:</p>



<pre class="wp-block-code"><code>@hostname=localhost
@port=99887
@host={{hostname}}:{{port}}
GET https://{{host}}/works</code></pre>



<h3>Environment files</h3>
<p>To assign different values to variables for different environments, create a file named <span class="code">http-client.env.json</span>. Place this file in the same directory as your <span class="code">.http</span> file or in any of its parent directories. Visual Studio uses this environment file to provide environment-specific variable values when sending HTTP requests.</p>
<p>The following example demonstrates the structure of an <span class="code">http-client.env.json</span> environment file:</p>



<pre class="wp-block-code"><code>{
  "dev": {
    "HostAddress": "https://localhost:99887"
  },
  "remote": {
    "HostAddress": "https://yogihosting.com"
  }
}</code></pre>



<p>An environment file is a JSON file that defines one or more named environments, such as <span class="code">dev</span> and <span class="code">remote</span>. Each environment contains one or more variables, such as <span class="code">HostAddress</span>, with values specific to that environment.</p>
<p>Variables defined in an environment file are referenced in the same way as regular <span class="code">.http</span> file variables, using double curly braces such as <span class="code">{{HostAddress}}</span>. The following example demonstrates how to reference an environment variable in an HTTP request:</p>



<pre class="wp-block-code"><code>GET {{HostAddress}}/works</code></pre>



<p>The value assigned to a variable when an HTTP request is sent depends on the <b>environment selected</b> from the environment selector dropdown in the upper-right corner of the <span class="code">.http</span> file editor. Select the required environment to use its corresponding variable values when executing the request. The following screenshot shows the environment selector:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/environment-selector-http-file.png" alt="Environment Selector http File" title="Environment Selector http File" class="img-fluid"></p>
<p>The environment file does not need to be located in the project folder. Visual Studio searches for a file named <span class="code">http-client.env.json</span> starting in the directory that contains the .http file. If the file is not found there, Visual Studio continues searching each parent directory until it finds one. The search stops as soon as a matching file is found, so the closest <span class="code">http-client.env.json</span> file to the <span class="code">.http</span> file takes precedence.</p>
<p>Visual Studio displays warnings in the following situations:</p>
<ul>
<li>The <span class="code">.http</span> file references a variable that is not defined in either the <span class="code">.http</span> file or the environment file.</li>
<li>The environment file defines a variable that is not referenced anywhere in the <span class="code">.http</span> file.</li>
</ul>
<p>A variable can be defined in both the <span class="code">.http</span> file and the environment file. When the same variable exists in both files, the value defined directly in the <span class="code">.http</span> file takes precedence over the value defined in the environment file.</p>
<h3>Shared variables</h3>
<p><span class="code">$shared</span> is a special environment name used to define variables whose values are common across multiple environments. Instead of repeating the same variable in each environment, you can define it once under $shared and reuse it across environments.</p>
<p>For example, consider the following <span class="term">http-client.env.json</span> environment file:</p>



<pre class="wp-block-code"><code>{
    "$shared": {
        "HostAddress": "https://localhost:99887"
    },
    "dev1": {
        "username": "Jack"
    },
    "dev2": {
        "username": "Alice"
    },
    "staging": {
        "username": "staginguser",
        "HostAddress": "https://staging.yogihosting.com"
    }
}</code></pre>



<p>In the preceding example, the $shared environment defines the <span class="term">HostAddress</span> variable with the value <span class="term">localhost:99887</span>. This value serves as a default for any environment that does not define its own <span class="term">HostAddress</span> variable.</p>
<P>Therefore, when the &#8220;dev1&#8221; or &#8220;dev2&#8221; environment is selected, <span class="term">HostAddress</span> uses the value from $shared because neither environment defines its own value. However, the staging environment defines HostAddress as <span class="term">https://staging.yogihosting.com</span>, so that value takes precedence over the $shared default.</p>
<h3>Request variables</h3>
<p>In a Visual Studio .http file, <b>request variables</b> are variables that are created from the response of one HTTP request and then used in subsequent requests. They are useful when one request depends on data returned by another request—for example, using an authentication token or an ID returned from a <span class="term">POST</span> request.</p>
<div class="note">Example: Request Variable usage</div>
<p>Request variables enable you to automate this process. For example, suppose an <span class="code">.http</span> file contains a request that authenticates the user and is named <span class="code">login</span>. The response from this request is a JSON object containing a bearer token in a property named <span class="code">token</span>. You can then use this token in subsequent requests by passing it in the <span class="term">Authorization header</span>. The following example demonstrates how to accomplish this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
@WorkApi_HostAddress = https://localhost:7026

# @name login

POST {{WorkApi_HostAddress}}/login 
Content-Type: application/json 

{ 
  &quot;name&quot;:&quot;eat breakfast&quot;, 
  &quot;isComplete&quot;:true, 
  &quot;timeStart&quot;:&quot;8:00:00&quot;, 
  &quot;timeEnd&quot;:&quot;8:30:00&quot; 
}

### 

GET {{WorkApi_HostAddress}}/works 
Authorization: Bearer {{login.response.body.$.token}} 

###
</pre></div>


<p>The expression <span class="code">{{login.response.body.$.token}}</span> is used to retrieve the bearer token from the response of the login request. Each part of the expression has a specific purpose:</p>
<ul>
<li>login: The name assigned to the HTTP request.</li>
<li>response: Refers to the response returned by that request.</li>
<li>body: Refers to the response body.</li>
<li>$: Represents the root of the JSON document in the response body.</li>
<li>token: Identifies the token property whose value is extracted from the JSON response.</li>
</ul>
<h3>User-specific environment files</h3>
<p>A user-specific value is a value that a developer needs for testing but does not want to share with other team members. Since the <span class="term">http-client.env.json</span> file is typically checked into source control, you should not store user-specific values in this file.
Instead, Visual Studio provides a separate file named <span class="term">http-client.env.json.user</span> for storing personal or local environment values. This file is located in the same folder as the <span class="term">http-client.env.json</span> file. Files with the .user extension are excluded from source control by default when using Visual Studio&#8217;s built-in source control features.</p>
<p>When Visual Studio loads an <span class="term">http-client.env.json</span> file, it automatically looks for a corresponding <span class="term">http-client.env.json.user</span> file in the same directory. If the same variable is defined in both files for the same environment, the value from the <span class="term">http-client.env.json.user</span> file takes precedence.</p>
<p>The following example demonstrates how a user-specific environment file works. Suppose the .http file contains the following content:</p>



<pre class="wp-block-code"><code>GET {{WorkApi_HostAddress}}/{{Path}}
Accept: application/json</code></pre>



<p>Let the <span class="term">http-client.env.json</span> file contains the following content:</p>



<pre class="wp-block-code"><code>{
  "dev": {
    "WorkApi_HostAddress": "https://localhost:99887",
    "Path": "works"
  },
  "remote": {
    "WorkApi_HostAddress": "https://yogihosting.com",
    "Path": "works"
  }
}</code></pre>



<p>If there&#8217;s a user-specific environment file that contains the following content:</p>



<pre class="wp-block-code"><code>{
  "dev": {
    "Path": "swagger/index.html"
  }
}</code></pre>



<p>When the user selects the &#8220;dev&#8221; environment, the request is sent to <span class="term">https://localhost:99887/swagger/index.html</span> because the Path value in the <span class="term">http-client.env.json.user</span> file overrides the value from the <span class="term">http-client.env.json</span> file.</p>
<p>With the same environment files, suppose the variables are defined in the <span class="code">.http</span> file:</p>



<pre class="wp-block-code"><code>@WorkApi_HostAddress=https://yogihosting.com
@Path=works

GET {{WorkApi_HostAddress}}/{{Path}}
Accept: application/json</code></pre>



<p>In this scenario, the &#8220;dev&#8221; environment request is sent to <span class="term">https://yogihosting.com/works</span> because variable definitions in <span class="term">.http</span> files override environment file definitions.</p>
<h3>ASP.NET Core User Secrets</h3>
<p>When testing ASP.NET Core Web APIs using <span class="code">.http</span> files in Visual Studio, you may need to work with sensitive values such as API keys, passwords, connection strings, or authentication tokens. These values should not be stored directly in the .http file or committed to source control.</p>
<p>ASP.NET Core provides User Secrets as a secure way to store sensitive development-time configuration values outside your project files.</p>
<p>For example, suppose your ASP.NET Core application has a secret named &#8220;ApiKey&#8221;. You can store it using the ASP.NET Core Secret Manager:</p>



<pre class="wp-block-code"><code>dotnet user-secrets set "ApiKey" "my-secret-api-key"</code></pre>



<p>The secret is stored outside the project directory and is not committed to source control.</p>
<p>Visual Studio can access ASP.NET Core User Secrets through environment variables defined in the <span class="code">http-client.env.json</span> file. For example:</p>



<pre class="wp-block-code"><code>{
  "dev": {
    "ApiKey": {
      "provider": "AspnetUserSecrets",
      "secretName": "ApiKey"
    }
  }
}</code></pre>



<p>Here:</p>
<ul>
<li><span class="term">ApiKey</span> — the variable name you will use in the .http file.</li>
<li><span class="term">&#8220;provider&#8221;: &#8220;AspnetUserSecrets&#8221;</span> — tells Visual Studio to get the value from ASP.NET Core User Secrets.</li>
<li><span class="term">&#8220;secretName&#8221;: &#8220;ApiKey&#8221;</span> — specifies the name of the User Secret to retrieve.</li>
</ul>
<p>You can then use the variable in your <span class="code">.http</span> file request:</p>



<pre class="wp-block-code"><code>GET https://localhost:5001/api/products
X-API-Key: {{ApiKey}}</code></pre>



<p>Here, <span class="code">{{ApiKey}}</span> is replaced with the value defined for the ApiKey variable when the request is executed.</p>
<p>When you send the request, Visual Studio retrieves the value of the ApiKey User Secret and uses it in the X-API-KEY header. The actual secret value isn&#8217;t displayed by the .http editor&#8217;s autocomplete.</p>
<p>So the flow is:</p>



<pre class="wp-block-code"><code>dotnet user-secrets set "ApiKey" "my-secret-api-key"
                    │
                    ▼
             ASP.NET Core
             User Secrets
                    │
                    ▼
        http-client.env.json
        provider: AspnetUserSecrets
                    │
                    ▼
             {{ApiKey}}
                    │
                    ▼
       X-API-KEY: &lt;secret value></code></pre>



<p>One important detail &#8211; The http-client.env.json file itself doesn&#8217;t contain the secret value, so it can safely be committed to source control:</p>



<pre class="wp-block-code"><code>{
  "dev": {
    "ApiKey": {
      "provider": "AspnetUserSecrets",
      "secretName": "ApiKey"
    }
  }
}</code></pre>



<p>The actual value remains in ASP.NET Core&#8217;s User Secrets store, outside the project. ASP.NET Core&#8217;s Secret Manager is specifically intended for keeping development secrets out of source-controlled project files.</p>
<h3>Azure Key Vault</h3>
<p>This below <span class="code">http-client.env.json</span> configuration tells Visual Studio&#8217;s .http file editor to retrieve a secret from Azure Key Vault and make it available as an HTTP client variable.</p>



<pre class="wp-block-code"><code>{
  "dev": {
    "AKVSecret": {
      "provider": "AzureKeyVault",
      "secretName": "SecretInKeyVault",
      "resourceId": "/subscriptions/3a914c59-8175-9e0e540/resourceGroups/my-key-vault-rg/providers/Microsoft.KeyVault/vaults/my-key-vault-01182024"
    }
  }
}</code></pre>



<p>Note: To retrieve a value from Azure Key Vault, you must be signed in to Visual Studio with an account that has the necessary permissions to access the target Key Vault.</p>
<p>The variable is named AKVSecret which  pulls its value from Azure Key Vault. Values of resourceId and secretName will be get from Azure portal.</p>
<p>The AKVSecret object defines the following properties:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Property</th><th>Description</th></tr></thead><tbody><tr><td><code>provider</code></td><td>Specifies the secret provider. For Azure Key Vault, set this value to <code>AzureKeyVault</code>.</td></tr><tr><td><code>secretName</code></td><td>Specifies the name of the secret to retrieve from Azure Key Vault.</td></tr><tr><td><code>resourceId</code></td><td>Specifies the Azure resource ID of the Key Vault that contains the secret.</td></tr></tbody></table></figure>



<p>Now the following .http file has a request that uses this secret value.</p>



<pre class="wp-block-code"><code>GET {{HostAddress}}{{Path}}
X-AKV-SECRET: {{akvSecret}}</code></pre>



<h3 class="wp-block-heading">Environment variables</h3>



<p class="wp-block-paragraph">$processEnv is a dynamic variable function in Visual Studio&#8217;s .http file editor that tells the HTTP client to read a value from the environment variables of the process running the request.</p>



<p class="wp-block-paragraph">Example:</p>



<pre class="wp-block-code"><code>GET {{HostAddress}}{{Path}}
X-UserName: {{$processEnv USERNAME}}</code></pre>



<p><span class="term">{{$processEnv USERNAME}}</span> &#8211; Get the value of the USERNAME environment variable. For example, if the process environment contains:</p>



<pre class="wp-block-code"><code>USERNAME=YogiHosting</code></pre>



<p>then:</p>



<pre class="wp-block-code"><code>X-UserName: {{$processEnv USERNAME}}</code></pre>



<p>effectively becomes:</p>



<pre class="wp-block-code"><code>X-UserName: Yogesh</code></pre>



<h3>.env files</h3>
<p>To retrieve the value of a variable defined in a <span class="code">.env</span> file, use the <span class="code">$dotenv</span> dynamic variable. The .env file must be located in the project folder. The syntax for $dotenv is similar to $processEnv. For example, if the .env file contains the following:</p>




<pre class="wp-block-code"><code>API_KEY=my-secret-api-key
API_URL=https://api.example.com</code></pre>



<p>You can access these values in a .http file using the $dotenv dynamic variable:</p>



<pre class="wp-block-code"><code>GET {{$dotenv API_URL}}/products
X-API-Key: {{$dotenv API_KEY}}</code></pre>



<p>When the request is executed, Visual Studio reads the values from the .env file. The request is effectively sent as:</p>



<pre class="wp-block-code"><code>GET https://api.example.com/products
X-API-Key: my-secret-api-key</code></pre>



<h3>Random integers</h3>
<p>To generate a random integer, use the <span class="term">$randomInt</span> dynamic variable. Its syntax is <span class="term">{{$randomInt [min max]}}</span>, where min and max are optional parameters that specify the minimum and maximum values for the generated integer.</p>
<p>Here are a few examples of using $randomInt in a Visual Studio .http file.</p>



<pre class="wp-block-code"><code>GET https://localhost:5001/api/products/{{$randomInt}}</code></pre>



<p>Visual Studio generates a random integer and replaces {{$randomInt}} with the generated value.</p>



<pre class="wp-block-code"><code>GET https://localhost:5001/api/products/42</code></pre>



<p>Generate a random integer within a range:</p>



<pre class="wp-block-code"><code>GET https://localhost:5001/api/products/{{$randomInt 1 100}}</code></pre>



<p>This generates a random integer between 1 and 100.</p>



<pre class="wp-block-code"><code>GET https://localhost:5001/api/products/73</code></pre>



<div class="note">Dates and times</div>
<ul>
<li>$datetime generates a datetime string in UTC.</li>
<li>$localDatetime generates a datetime string in the local time zone.</li>
<li>$timestamp generates a timestamp in UTC. </li>
</ul>
<p>Example:</p>




<pre class="wp-block-code"><code>GET https://localhost:5001/api/orders?createdAt={{$datetime}}</code></pre>



<p>It might produce:</p>



<pre class="wp-block-code"><code>GET https://localhost:5001/api/orders?createdAt=2026-08-15T13:20:30Z</code></pre>



<p>The link to download the full source code of this tutorial is given below:</p>
<p><a class="testLink" href="https://www.yogihosting.com/wp-content/themes/yogi-yogihosting/download/aspnetcore/MinimalAPI.zip" target="_blank" rel="noopener">Download</a></p>
<div class="note">Conclusion</div>
<p>Overall, .http files are an effective tool for API development, debugging, automated test scenarios, and sharing reproducible HTTP requests with your development team. By using environment files and secure secret providers appropriately, you can keep your API testing workflow both flexible and secure.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-http-editor/">Effortless ASP.NET Core Minimal API Testing with the Visual Studio HTTP Editor</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-http-editor/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to use JSON Patch in ASP.NET Core Minimal API</title>
		<link>https://www.yogihosting.com/aspnet-core-json-patch-web-api/</link>
					<comments>https://www.yogihosting.com/aspnet-core-json-patch-web-api/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Sat, 29 Aug 2026 04:28:18 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=23477</guid>

					<description><![CDATA[<p>JSON Patch is a standardized format for describing changes to a JSON document. It is defined by RFC 6902 and is commonly used with HTTP PATCH requests to update resources without sending the entire document. A JSON Patch document is itself a JSON array of operations. Each operation specifies: op: the operation to perform path: [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-json-patch-web-api/">How to use JSON Patch in ASP.NET Core Minimal API</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">JSON Patch is a standardized format for describing changes to a JSON document. It is defined by RFC 6902 and is commonly used with HTTP PATCH requests to update resources without sending the entire document.</p>



<span id="more-23477"></span>



<p>A JSON Patch document is itself a JSON array of operations. Each operation specifies:</p>
<ul>
<li>op: the operation to perform</li>
<li>path: a JSON Pointer identifying the target location</li>
<li>Additional fields such as value or from, depending on the operation</li>
</ul>
<div class="note">Example:</div>
<p>Original document:</p>



<pre class="wp-block-code"><code>{
  "name": "Alice",
  "age": 30,
  "tags": &#91;"admin"]
}</code></pre>



<p class="wp-block-paragraph">Patch:</p>



<pre class="wp-block-code"><code>&#91;
  {
    "op": "replace",
    "path": "/age",
    "value": 31
  },
  {
    "op": "add",
    "path": "/tags/1",
    "value": "editor"
  }
]</code></pre>



<p class="wp-block-paragraph">Result:</p>



<pre class="wp-block-code"><code>{
  "name": "Alice",
  "age": 31,
  "tags": &#91;"admin", "editor"]
}</code></pre>



<p><span class="term">/tags/1 -&gt; Index 1</span> is the position immediately after the first element &#8220;admin&#8221;, so &#8220;editor&#8221; is inserted there. So it becomes &#8220;admin&#8221;, &#8220;editor&#8221;.</p>
<p>Supported operations:</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Operation</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td>add</td>
<td>Add a value to an object or array.</td>
</tr>
<tr>
<td>remove</td>
<td>Remove a value.</td>
</tr>
<tr>
<td>replace</td>
<td>Replace an existing value.</td>
</tr>
<tr>
<td>move</td>
<td>Move a value from one location to another.</td>
</tr>
<tr>
<td>copy</td>
<td>Copy a value from one location to another.</td>
</tr>
<tr>
<td>test</td>
<td>Verify that a value matches an expected value before continuing.</td>
</tr>
</tbody>
</table>
</div>
<div class="note">Examples:</div>
<p>Add a property:</p>



<pre class="wp-block-code"><code>{
  "op": "add",
  "path": "/email",
  "value": "alice@example.com"
}</code></pre>



<p class="wp-block-paragraph">Remove a property:</p>



<pre class="wp-block-code"><code>{
  "op": "remove",
  "path": "/email"
}</code></pre>



<p class="wp-block-paragraph">Move a value:</p>



<pre class="wp-block-code"><code>{
  "op": "move",
  "from": "/oldName",
  "path": "/newName"
}</code></pre>



<p class="wp-block-paragraph">Test before replacing:</p>



<pre class="wp-block-code"><code>&#91;
  {
    "op": "test",
    "path": "/version",
    "value": 5
  },
  {
    "op": "replace",
    "path": "/version",
    "value": 6
  }
]</code></pre>



<p>If the <span class="code">test</span> operation fails, the entire patch fails.</p>
<p>JSON Patch is useful when:</p>
<ul>
<li>Updating REST API resources via HTTP PATCH</li>
<li>Synchronizing documents between clients and servers</li>
<li>Tracking edits efficiently</li>
<li>Applying incremental changes in collaborative applications</li>
</ul>
<p>It is especially valuable when only a small portion of a large JSON document changes, because it avoids sending the entire document.</p>
<h3>JSON Patch vs HTTP Patch</h3>
<p>JSON Patch and HTTP PATCH are related, but they are not the same thing.</p>
<ul>
<li>HTTP PATCH is an HTTP method (like GET, POST, PUT, DELETE).</li>
<li>JSON Patch is a document format (RFC 6902) that is commonly used as the payload of an HTTP PATCH request.</li>
</ul>
<p>Relationship:</p>



<pre class="wp-block-code"><code>HTTP PATCH (method)
        │
        ├── JSON Patch (RFC 6902)
        ├── JSON Merge Patch (RFC 7396)
        └── Custom partial update format</code></pre>



<p>Note that a HTTP PATCH request can use different formats for its request body. JSON Patch is just one of them.</p>
<div class="note">HTTP PATCH</div>
<p>HTTP PATCH tells the server: &#8220;Update only part of this resource.&#8221;</p>



<pre class="wp-block-code"><code>PATCH /users/1 HTTP/1.1
Content-Type: application/json

{
  "age": 31
}</code></pre>



<p>The HTTP specification does not define what the body should look like. That depends on the API.</p>
<div class="note">JSON Patch</div>
<p>JSON Patch defines how to describe changes.</p>



<pre class="wp-block-code"><code>PATCH /users/1 HTTP/1.1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "replace",
    "path": "/age",
    "value": 31
  }
]</code></pre>



<h2>JSON PATCH in ASP.NET Core Minimal API</h2>
<p>In ASP.NET Core Minimal APIs, you can support JSON Patch (application/json-patch+json) using the <span class="term">Microsoft.AspNetCore.JsonPatch</span> package. JSON Patch follows RFC 6902 and allows partial updates with operations like add, remove, replace, move, copy, and test.</p>
<p>1. Install the package</p>



<pre class="wp-block-code"><code>dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson</code></pre>



<p>2. Create Models</p>
<p>Here we have Department and Employee class where Departments contains one or many Employees.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class Department
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Manager { get; set; }
    public List&lt;Employee&gt;? Employees { get; set; }
}

public class Employee
{
    public int Id { get; set; }

    public string Name { get; set; }
    public DateTime JoiningDate { get; set; }
    public decimal Salary { get; set; }
}
</pre></div>


<p>3. JSON Patch code</p>
<p>This endpoint implements a JSON Patch endpoint in an ASP.NET Core Minimal API. It retrieves a Department from the database, applies the JSON Patch operations, reports any patch errors as validation errors, saves the changes, and returns the updated entity.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapPatch(&quot;/customers/{id}&quot;, async Task&lt;Results&lt;Ok&lt;Department&gt;, ValidationProblem, NotFound&lt;ProblemDetails&gt;&gt;&gt; (WorkDb db, int id,
    JsonPatchDocument&lt;Department&gt; patchDoc) =&gt;
{
    var department = await db.Departments.Include(c =&gt; c.Employees).FirstOrDefaultAsync(c =&gt; c.Id == id);
    
    if (department is null)
        return TypedResults.NotFound&lt;ProblemDetails&gt;(new());
    
    if (patchDoc != null)
    {
        Dictionary&lt;string, string&#x5B;]&gt;? errors = null;
        patchDoc.ApplyTo(department, jsonPatchError =&gt;
        {
            errors ??= new();
            var key = jsonPatchError.AffectedObject.GetType().Name;
            if (!errors.ContainsKey(key))
            {
                errors.Add(key, new string&#x5B;] { });
            }
            errors&#x5B;key] = errors&#x5B;key].Append(jsonPatchError.ErrorMessage).ToArray();
        });

        if (errors != null)
        {
            return TypedResults.ValidationProblem(errors);
        }

        // Only save if there are no errors
        await db.SaveChangesAsync();
    }

    return TypedResults.Ok(department);
})
.Accepts&lt;JsonPatchDocument&lt;Department&gt;&gt;(&quot;application/json-patch+json&quot;);
</pre></div>


<div class="note">Explanation:</div>
<p><b>Retrieve the Department:</b></p>
<p>The endpoint retrieves a Department object from the database from the provided id. When no department is found then it returns 404 Not Found response via <span class="code">TypedResults.NotFound()</span> method.</p>
<p><b>Apply JSON Patch:</b></p>
<p>The <span class="code">ApplyTo(Object)</span> method executes the JSON Patch operations defined in patchDoc on the retrieved Department object. If any errors occur while applying the patch, such as invalid paths, unsupported operations, or conflicting changes, the provided error-handling delegate is invoked. The delegate captures these errors and stores the corresponding error messages in a dictionary, using the affected object&#8217;s type name as the key.</p>
<p><b>Return validation errors:</b></p>
<p>If the error-handling delegate detects any issues while applying the patch operations, the endpoint returns a ValidationProblem response using <span class="code">TypedResults.ValidationProblem(errors)</span>, which includes the collected error details.</p>
<p><b>Save and return the Updated Department:</b></p>
<p>If the patch operations are applied successfully without any errors, the changes are persisted to the database using <span class="code">SaveChangesAsync()</span>. The endpoint then returns the updated Customer object in an OK response through <span class="code">TypedResults.Ok(department)</span>.</p>
<h3>Example error response</h3>
<p>The following example demonstrates the response body of a validation problem returned for a JSON Patch operation when the specified path is invalid:</p>



<pre class="wp-block-code"><code>{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Department": &#91;
      "The target location specified by path segment 'Admin' was not found."
    ]
  }
}</code></pre>



<h2>Testing with .http File Editor in Visual Studio</h2>
<p>We have 2 Departments and each department containing 2 Employees. See below structure:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;
  {
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;Development&quot;,
    &quot;manager&quot;: &quot;Adam Dorsey&quot;,
    &quot;employees&quot;: &#x5B;
      {
        &quot;id&quot;: 1,
        &quot;name&quot;: &quot;John Doe&quot;,
        &quot;joiningDate&quot;: &quot;2024-08-04T20:18:50.1041658+05:30&quot;,
        &quot;salary&quot;: 60000
      },
      {
        &quot;id&quot;: 2,
        &quot;name&quot;: &quot;Jane Smith&quot;,
        &quot;joiningDate&quot;: &quot;2025-08-04T20:18:50.1042795+05:30&quot;,
        &quot;salary&quot;: 55000
      }
    ]
  },
  {
    &quot;id&quot;: 2,
    &quot;name&quot;: &quot;Support&quot;,
    &quot;manager&quot;: &quot;Elon Musk&quot;,
    &quot;employees&quot;: &#x5B;
      {
        &quot;id&quot;: 3,
        &quot;name&quot;: &quot;Alice Johnson&quot;,
        &quot;joiningDate&quot;: &quot;2023-08-04T20:18:50.1042808+05:30&quot;,
        &quot;salary&quot;: 70000
      },
      {
        &quot;id&quot;: 4,
        &quot;name&quot;: &quot;Bob Brown&quot;,
        &quot;joiningDate&quot;: &quot;2022-08-04T20:18:50.104281+05:30&quot;,
        &quot;salary&quot;: 65000
      }
    ]
  }
]
</pre></div>


<p class="wp-block-paragraph">To replace the 1st Manager name from John Doe to Jack Doe, the Patch request is:</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "replace",
    "path": "/Manager",
    "value": "Jack Doe"
  }
]</code></pre>



<p>We used replace for &#8220;op&#8221; and given path as &#8220;/Manager&#8221; with value containing the new name i.e. Jack Doe.</p>
<p>Lets send a Patch request to add a new Employee in the 1st department. The patch request is given below.</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "add",
    "path": "/Employees/-",
    "value": {
      "name": "Yogi S",
      "joiningDate": "2026-08-04T00:00:00",
      "salary": 165000
    }
  }
]</code></pre>



<p>Why /Employees/-? In JSON Patch:</p>
<ul>
<li>/Employees/0 → insert at index 0</li>
<li>/Employees/1 → insert at index 1</li>
<li>/Employees/- → append to the end of the array</li>
</ul>
<p>The &#8220;-&#8221; is defined by the JSON Patch specification to mean &#8220;append&#8221;.</p>
<p>To insert at index 0:</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "add",
    "path": "/Employees/0",
    "value": {
      "name": "Alice Johnson",
      "joiningDate": "2026-08-04T00:00:00",
      "salary": 65000
    }
  }
]</code></pre>



<p>Update an existing employee &#8211; For example, change the first employee&#8217;s salary:</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "replace",
    "path": "/Employees/0/Salary",
    "value": 70000
  }
]</code></pre>



<p>Remove the second employee:</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "remove",
    "path": "/Employees/1"
  }
]</code></pre>



<p>More complicated scenarios include replacing Manger name and at the same time adding a new Employee. Check the below request which does this thing in one request only.</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "replace",
    "path": "/Manager",
    "value": "Jack Doe"
  },
  {
    "op": "add",
    "path": "/Employees/-",
    "value": {
      "name": "Yogi S",
      "joiningDate": "2026-08-04T00:00:00",
      "salary": 165000
    }
  }
]</code></pre>



<h3>Test in JSON Patch</h3>
<p>JSON Patch supports the test operation, which verifies that the value of a specified property matches an expected value. If the values do not match, the operation fails and the server returns an error without applying the patch.</p>
<p>In the below patch request we test if the Department name is &#8220;Developement&#8221; only then replace manager to &#8220;Yogi S.&#8221;.</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "test",
    "path": "/Name",
    "value": "Development"
  },
  {
    "op": "replace",
    "path": "/Manager",
    "value": "Yogi S"
  }
]</code></pre>



<p>Suppose we send the Name as &#8220;Testing&#8221;. See below request.</p>



<pre class="wp-block-code"><code>PATCH {{DailyWork_HostAddress}}/departments/1
Content-Type: application/json-patch+json

&#91;
  {
    "op": "test",
    "path": "/Name",
    "value": "Testing"
  },
  {
    "op": "replace",
    "path": "/Manager",
    "value": "Yogi S"
  }
]</code></pre>



<p class="wp-block-paragraph">Since here the department name does not match so we get an error response.</p>



<pre class="wp-block-code"><code>{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Department": &#91;
      "The current value 'Development' at path 'Name' is not equal to the test value 'Testing'."
    ]
  }
}</code></pre>



<p>Certainly this is a helpful feature to prevent unwanted changes.</p>
<div class="note">Conclusion</div>
<p>In this tutorial we learned how to use JSON PATCH in ASP.NET Core Minimal API. We also checked all the examples to understand it&#8217;s uses. If you find it useful then please share it on your social media account with your friends.</p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-json-patch-web-api/">How to use JSON Patch in ASP.NET Core Minimal API</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-json-patch-web-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to perform Unit and Integration Testing in Minimal APIs</title>
		<link>https://www.yogihosting.com/aspnet-core-minimal-api-testing/</link>
					<comments>https://www.yogihosting.com/aspnet-core-minimal-api-testing/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Tue, 11 Aug 2026 03:39:16 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=23181</guid>

					<description><![CDATA[<p>Testing is an essential part of developing ASP.NET Core Minimal APIs because it ensures that API endpoints behave correctly, reliably, and securely. Unit testing verifies the business logic of individual components, while integration testing confirms that endpoints, routing, middleware, dependency injection, and database interactions work together as expected. Regular testing helps identify bugs early, improves [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-testing/">How to perform Unit and Integration Testing in Minimal APIs</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Testing is an essential part of developing ASP.NET Core Minimal APIs because it ensures that API endpoints behave correctly, reliably, and securely. Unit testing verifies the business logic of individual components, while integration testing confirms that endpoints, routing, middleware, dependency injection, and database interactions work together as expected. Regular testing helps identify bugs early, improves code quality, simplifies maintenance, and provides confidence when making changes or adding new features. As a result, testing contributes to building robust, scalable, and maintainable Minimal API applications.</p>



<span id="more-23181"></span>



<p>In this tutorial we will be performing both <b>Unit and Integration Testing in Minimal APIs</b>.</p>
<h2>ASP.NET Core Minimal API Project</h2>
<p>On our last tutorial we created our <a href="https://www.yogihosting.com/aspnet-core-minimal-api/">ASP.NET Core Minimal API from Start till Finish</a>. The Program.cs class creates a  route group in an ASP.NET Core Minimal API and registers all endpoints related to the Works resource under the <span class="term">/works</span> base URL.</p>
<div class="starBlock">No need to worry about the codes as you can download this project from the download button given at the bottom of this tutorial.</div>
<ul>
<li><span class="code">app.MapGroup(&#8220;/works&#8221;)</span> creates a route group with the common prefix <span class="term">/works</span>.</li>
<li><span class="code">WorkAPIV1()</span> &#038; <span class="code">WorkAPIV2()</span> are 2 custom extension method that maps all the endpoints (such as GET, POST, PUT, and DELETE) to that route group.</li>
<li>We have 2 sets of Minimal API for these 2 custom extension methods. The first starts with <span class="term">/works/v1</span> while second starts with <span class="term">/works/v2</span>.</li>
<li>We also registered an In-Memory database and an interface called <span class="term">IWorkService</span>.</li>
</ul>
<p>See the code below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using DailyWork;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient&lt;IWorkService, WorkService&gt;();

builder.Services.AddDbContext&lt;WorkDb&gt;(opt =&gt; opt.UseInMemoryDatabase(&quot;WorkDatabase&quot;));

var app = builder.Build();

app.MapGet(&quot;/&quot;, () =&gt; &quot;Welcome to Minimal API Project&quot;);

app.MapGroup(&quot;/works/v1&quot;).WorkAPIV1();
app.MapGroup(&quot;/works/v2&quot;).WorkAPIV2();

app.Run();
</pre></div>


<p>The <span class="term">Work.cs</span> class is:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class Work
{
	public int Id { get; set; }
	public string Name { get; set; }

	public string TimeStart { get; set; }

	public string TimeEnd { get; set; }

	public bool IsComplete { get; set; }

    public string? Secret { get; set; }
}
</pre></div>


<p>Next, there is a static class called <u>WorkEndpointV1</u> that contains the custom extension method called <span class="code">WorkAPIV1()</span>.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public static class WorkEndpointV1
{
    public static RouteGroupBuilder WorkAPIV1(this RouteGroupBuilder workGroup)
    {
        workGroup.MapPost(&quot;/&quot;, CreateWork)
                 .AddEndpointFilter(async (invocationContext, next) =&gt;
                 {
                     var w = invocationContext.GetArgument&lt;Work&gt;(0);

                     var validationErrors = Utilities.IsValid(w);

                     if (validationErrors.Any())
                     {
                         return Results.ValidationProblem(validationErrors);
                     }

                     return await next(invocationContext);
                 });
        workGroup.MapGet(&quot;/&quot;, GetAllWork);
        workGroup.MapGet(&quot;/complete&quot;, GetCompletedWork);
        workGroup.MapGet(&quot;/{id}&quot;, GetWorkById);
        workGroup.MapPut(&quot;/{id}&quot;, UpdateWorkById);
        workGroup.MapPatch(&quot;/{id}&quot;, UpdateWorkByIdWithPatch);
        workGroup.MapDelete(&quot;/{id}&quot;, DeleteWorkById);

        return workGroup;
    }

    public static async Task&lt;Created&lt;Work&gt;&gt; CreateWork(Work work, WorkDb db)
    {
        db.Works.Add(work);
        await db.SaveChangesAsync();

        return TypedResults.Created($&quot;/works/{work.Id}&quot;, work);
    }

    public static async Task&lt;Ok&lt;Work&#x5B;]&gt;&gt; GetAllWork(WorkDb db)
    {
        return TypedResults.Ok(await db.Works.ToArrayAsync());
    }

    public static async Task&lt;Ok&lt;Work&#x5B;]&gt;&gt; GetCompletedWork(WorkDb db)
    {
        return TypedResults.Ok(await db.Works.Where(t =&gt; t.IsComplete).ToArrayAsync());
    }

    public static async Task&lt;Results&lt;Ok&lt;Work&gt;, NotFound&gt;&gt; GetWorkById(int id, WorkDb db)
    {
        return await db.Works.FindAsync(id)
            is Work work
                ? TypedResults.Ok(work)
                : TypedResults.NotFound();
    }

    public static async Task&lt;Results&lt;NoContent, NotFound&gt;&gt; UpdateWorkById(int id, Work work, WorkDb db)
    {
        var w = await db.Works.FindAsync(id);

        if (w is null) return TypedResults.NotFound();

        w.Name = work.Name;
        w.TimeStart = work.TimeStart;
        w.TimeEnd = work.TimeEnd;
        w.IsComplete = work.IsComplete;

        await db.SaveChangesAsync();

        return TypedResults.NoContent();
    }
    public static async Task&lt;IResult&gt; UpdateWorkByIdWithPatch(int id, WorkDto workDto, WorkDb db)
    {
        var w = await db.Works.FindAsync(id);

        if (w is null) return TypedResults.NotFound();

        if (workDto.Name is not null) w.Name = workDto.Name;
        if (workDto.IsComplete is not null) w.IsComplete = workDto.IsComplete.Value;
        if (workDto.TimeStart is not null) w.TimeStart = workDto.TimeStart;
        if (workDto.TimeEnd is not null) w.TimeEnd = workDto.TimeEnd;

        await db.SaveChangesAsync();

        return TypedResults.NoContent();
    }

    public static async Task&lt;Results&lt;NoContent, NotFound&gt;&gt; DeleteWorkById(int id, WorkDb db)
    {
        if (await db.Works.FindAsync(id) is Work work)
        {
            db.Works.Remove(work);
            await db.SaveChangesAsync();
            return TypedResults.NoContent();
        }

        return TypedResults.NotFound();
    }
}
</pre></div>


<p>The above code simply performs the CRUD operations when the api GET, POST, PUT, and DELETE endpoints are called.</p>
<p>We also define a <span class="term">Utilities.cs</span> class which is called by the filter for validating the Work class Name field. This makes sure that the Name field is not empty and should have more than 2 characters in length.</p>



<pre class="wp-block-code"><code>public static class Utilities
{
    public static Dictionary&lt;string, string&#91;]> IsValid(Work w)
    {
        Dictionary&lt;string, string&#91;]> errors = new();

        if (string.IsNullOrEmpty(w.Name))
        {
            errors.TryAdd("work.name.errors", new&#91;] { "Name is empty" });
        }

        if (w.Name.Length &lt; 3)
        {
            errors.TryAdd("work.name.errors", new&#91;] { "Name length &lt; 3" });
        }

        return errors;
    }
}</code></pre>



<p>Moving to static class called <span class="term">WorkEndpointV2</span> that contains the custom extension method called <span class="code">WorkAPIV2()</span>. See it&#8217;s code below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public static class WorkEndpointV2
{
    public static RouteGroupBuilder WorkAPIV2(this RouteGroupBuilder workGroup)
    {
        workGroup.MapPost(&quot;/&quot;, CreateWork)
                 .AddEndpointFilter(async (invocationContext, next) =&gt;
                 {
                     var w = invocationContext.GetArgument&lt;Work&gt;(0);

                     var validationErrors = Utilities.IsValid(w);

                     if (validationErrors.Any())
                     {
                         return Results.ValidationProblem(validationErrors);
                     }

                     return await next(invocationContext);
                 });
        workGroup.MapGet(&quot;/&quot;, GetAllWork);
        workGroup.MapGet(&quot;/complete&quot;, GetCompletedWork);
        workGroup.MapGet(&quot;/{id}&quot;, GetWorkById);
        workGroup.MapPut(&quot;/{id}&quot;, UpdateWorkById);
        workGroup.MapPatch(&quot;/{id}&quot;, UpdateWorkByIdWithPatch);
        workGroup.MapDelete(&quot;/{id}&quot;, DeleteWorkById);

        return workGroup;
    }
    public static async Task&lt;Created&lt;Work&gt;&gt; CreateWork(Work work, IWorkService workService)
    {
        await workService.Add(work);

        return TypedResults.Created($&quot;/works/{work.Id}&quot;, work);
    }

    public static async Task&lt;Ok&lt;List&lt;Work&gt;&gt;&gt; GetAllWork(IWorkService workService)
    {
        return TypedResults.Ok(await workService.GetAll());
    }

    public static async Task&lt;Ok&lt;List&lt;Work&gt;&gt;&gt; GetCompletedWork(IWorkService workService)
    {
        return TypedResults.Ok(await workService.GetCompleteWork());
    }

    public static async Task&lt;Results&lt;Ok&lt;Work&gt;, NotFound&gt;&gt; GetWorkById(int id, IWorkService workService)
    {
        return await workService.Find(id)
            is Work work
                ? TypedResults.Ok(work)
                : TypedResults.NotFound();
    }

    public static async Task&lt;Results&lt;NoContent, NotFound&gt;&gt; UpdateWorkById(int id, Work work, IWorkService workService)
    {
        var w = await workService.Find(id);

        if (w is null) return TypedResults.NotFound();

        w.Name = work.Name;
        w.TimeStart = work.TimeStart;
        w.TimeEnd = work.TimeEnd;
        w.IsComplete = work.IsComplete;

        await workService.Update(w);

        return TypedResults.NoContent();
    }

    public static async Task&lt;IResult&gt; UpdateWorkByIdWithPatch(int id, WorkDto workDto, IWorkService workService)
    {
        var w = await workService.Find(id);

        if (w is null) return TypedResults.NotFound();

        if (workDto.Name is not null) w.Name = workDto.Name;
        if (workDto.IsComplete is not null) w.IsComplete = workDto.IsComplete.Value;
        if (workDto.TimeStart is not null) w.TimeStart = workDto.TimeStart;
        if (workDto.TimeEnd is not null) w.TimeEnd = workDto.TimeEnd;

        await workService.Update(w);

        return TypedResults.NoContent();
    }

    public static async Task&lt;Results&lt;NoContent, NotFound&gt;&gt; DeleteWorkById(int id, IWorkService workService)
    {
        if (await workService.Find(id) is Work work)
        {
            await workService.Remove(work);
            return TypedResults.NoContent();
        }

        return TypedResults.NotFound();
    }
}
</pre></div>


<p>It uses Repository Pattern to perform CRUD operation when API endpoints are called. For this we define an interface by the name of <span class="term">IWorkService</span> as given below.</p>



<pre class="wp-block-code"><code>public interface IWorkService
{
    Task&lt;List&lt;Work>> GetAll();

    Task&lt;List&lt;Work>> GetCompleteWork();

    ValueTask&lt;Work?> Find(int id);

    Task Add(Work w);

    Task Update(Work w);

    Task UpdatePatch(Work w);

    Task Remove(Work w);
}</code></pre>



<p>The class <span class="term">WorkService.cs</span> implements this interface. See code below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class WorkService : IWorkService
{
    private readonly WorkDb _dbContext;

    public WorkService(WorkDb dbContext)
    {
        _dbContext = dbContext;
    }

    public async ValueTask&lt;Work?&gt; Find(int id)
    {
        return await _dbContext.Works.FindAsync(id);
    }

    public async Task&lt;List&lt;Work&gt;&gt; GetAll()
    {
        return await _dbContext.Works.ToListAsync();
    }

    public async Task Add(Work work)
    {
        await _dbContext.Works.AddAsync(work);

        await _dbContext.SaveChangesAsync();
    }

    public async Task Update(Work work)
    {
        _dbContext.Works.Update(work);
        await _dbContext.SaveChangesAsync();
    }

    public async Task UpdatePatch(Work work)
    {
        _dbContext.Works.Update(work);
        await _dbContext.SaveChangesAsync();
    }

    public async Task Remove(Work work)
    {
        _dbContext.Works.Remove(work);
        await _dbContext.SaveChangesAsync();
    }

    public Task&lt;List&lt;Work&gt;&gt; GetCompleteWork()
    {
        return _dbContext.Works.Where(w =&gt; w.IsComplete == true).ToListAsync();
    }
}
</pre></div>


<p>The CRUD operation are performed by calling <u>WorkService.cs&#8221;</u> class like:</p>



<pre class="wp-block-code"><code>workService.Add(work); // CREATE RECORD
workService.GetAll() // READ RECORD
workService.Update(w); // UPDATE RECORD
workService.Remove(work) // DELETE RECORD</code></pre>



<h2>Performing Unit Testing in ASP.NET Core Minimal API</h2>

<p>Unit testing is a software testing technique used to verify the correctness of individual components of an application in isolation. A unit typically represents the smallest testable part of the software, such as a single method, function, or class. The primary objective of unit testing is to ensure that each component performs its intended functionality independently, without relying on external systems such as databases, web services, or file systems. By isolating dependencies through techniques such as mocking or stubbing, developers can focus on validating the business logic of a specific unit.</p>
<p>In ASP.NET Core applications, unit tests are commonly written using testing frameworks such as xUnit. A dedicated test project is created to contain the test cases, and a test runner is used to discover, execute, and report the results of the tests.</p>
<p>Right click on the solution to add a new class project and name it <u>DailyWorkTest</u>. This project will contain the Unit Test cases.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/unit-test-minimal-api.png" alt="Unit Test Minimal API" title="Unit Test Minimal API" class="img-fluid"></p>
<p>Now add the following packages to this newly added project. These packages are:</p>
<ul>
<li>xUnit.net v3</li>
<li>Microsoft.NET.Test.Sdk</li>
<li>xunit.runner.visualstudio</li>
<li>Moq</li>
</ul>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/xunit-testing.png" alt="xUnit Testing" title="xUnit Testing" class="img-fluid"></p>
<p>Lets add unit test cases. So add a new class called <span class="term">WorkUnitTest.cs</span> to this new project. In this class we will define our unit tests. See it&#8217;s code below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class WorkUnitTest
{
    &#x5B;Fact]
    public async Task GetWorkByIdIfNotExists()
    {
        // Arrange
        await using var context = new MockDB().CreateDbContext();

        // Act
        var result = await WorkEndpointV1.GetWorkById(1, context);

        Assert.IsType&lt;Results&lt;Ok&lt;Work&gt;, NotFound&gt;&gt;(result);

        var notFoundResult = (NotFound)result.Result;

        Assert.NotNull(notFoundResult);
    }

    &#x5B;Fact]
    public async Task GetAllWork()
    {
        // Arrange
        await using var context = new MockDB().CreateDbContext();

        context.Works.Add(new Work
        {
            Name = &quot;Test Name 1&quot;,
            TimeStart = &quot;Test TimeStart 1&quot;,
            TimeEnd = &quot;Test TimeEnd 1&quot;,
            IsComplete = false
        });

        context.Works.Add(new Work
        {
            Name = &quot;Test Name 2&quot;,
            TimeStart = &quot;Test TimeStart 2&quot;,
            TimeEnd = &quot;Test TimeEnd 2&quot;,
            IsComplete = true
        });

        await context.SaveChangesAsync();

        // Act
        var result = await WorkEndpointV1.GetAllWork(context);

        //Assert
        Assert.IsType&lt;Ok&lt;Work&#x5B;]&gt;&gt;(result);

        Assert.NotNull(result.Value);
        Assert.NotEmpty(result.Value);
        Assert.Collection(result.Value, work1 =&gt;
        {
            Assert.Equal(1, work1.Id);
            Assert.Equal(&quot;Test Name 1&quot;, work1.Name);
            Assert.False(work1.IsComplete);
        }, work2 =&gt;
        {
            Assert.Equal(2, work2.Id);
            Assert.Equal(&quot;Test Name 2&quot;, work2.Name);
            Assert.True(work2.IsComplete);
        });
    }

    &#x5B;Fact]
    public async Task GetCompletedWork()
    {
        // Arrange
        await using var context = new MockDB().CreateDbContext();

        context.Works.Add(new Work
        {
            Name = &quot;Test Name 1&quot;,
            TimeStart = &quot;Test TimeStart 1&quot;,
            TimeEnd = &quot;Test TimeEnd 1&quot;,
            IsComplete = false
        });

        context.Works.Add(new Work
        {
            Name = &quot;Test Name 2&quot;,
            TimeStart = &quot;Test TimeStart 2&quot;,
            TimeEnd = &quot;Test TimeEnd 2&quot;,
            IsComplete = true
        });

        await context.SaveChangesAsync();

        // Act
        var result = await WorkEndpointV1.GetCompletedWork(context);

        //Assert
        Assert.IsType&lt;Ok&lt;Work&#x5B;]&gt;&gt;(result);

        Assert.NotNull(result.Value);
        Assert.NotEmpty(result.Value);
        Assert.Collection(result.Value, w =&gt;
        {
            Assert.Equal(2, w.Id);
            Assert.Equal(&quot;Test Name 2&quot;, w.Name);
            Assert.True(w.IsComplete);
        });
    }

    &#x5B;Fact]
    public async Task GetWorkById()
    {
        // Arrange
        await using var context = new MockDB().CreateDbContext();

        context.Works.Add(new Work
        {
            Name = &quot;Test Name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        });

        await context.SaveChangesAsync();

        // Act
        var result = await WorkEndpointV1.GetWorkById(1, context);

        //Assert
        Assert.IsType&lt;Results&lt;Ok&lt;Work&gt;, NotFound&gt;&gt;(result);

        var okResult = (Ok&lt;Work&gt;)result.Result;

        Assert.NotNull(okResult.Value);
        Assert.Equal(1, okResult.Value.Id);
    }

    &#x5B;Fact]
    public async Task CreateWork()
    {
        //Arrange
        await using var context = new MockDB().CreateDbContext();

        var newWork = new Work
        {
            Name = &quot;Test Name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        };

        //Act
        var result = await WorkEndpointV1.CreateWork(newWork, context);

        //Assert
        Assert.IsType&lt;Created&lt;Work&gt;&gt;(result);

        Assert.NotNull(result);
        Assert.NotNull(result.Location);

        Assert.NotEmpty(context.Works);
        Assert.Collection(context.Works, work =&gt;
        {
            Assert.Equal(1, work.Id);
            Assert.Equal(&quot;Test Name&quot;, work.Name);
            Assert.Equal(&quot;Test TimeStart&quot;, work.TimeStart);
            Assert.Equal(&quot;Test TimeEnd&quot;, work.TimeEnd);
            Assert.False(work.IsComplete);
        });
    }

    &#x5B;Fact]
    public async Task UpdateWork()
    {
        //Arrange
        await using var context = new MockDB().CreateDbContext();

        context.Works.Add(new Work
        {
            Name = &quot;Exiting test name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        });

        await context.SaveChangesAsync();

        var updatedWork = new Work
        {
            Name = &quot;Updated test name&quot;,
            TimeStart = &quot;Updated Test TimeStart&quot;,
            TimeEnd = &quot;Updated Test TimeEnd&quot;,
            IsComplete = true
        };

        //Act
        var result = await WorkEndpointV1.UpdateWorkById(1, updatedWork, context);

        //Assert
        Assert.IsType&lt;Results&lt;NoContent, NotFound&gt;&gt;(result);

        var noContentResult = (NoContent)result.Result;

        Assert.NotNull(noContentResult);

        var workInDb = await context.Works.FindAsync(1);

        Assert.NotNull(workInDb);
        Assert.Equal(&quot;Updated test name&quot;, workInDb!.Name);
        Assert.True(workInDb.IsComplete);
    }

    &#x5B;Fact]
    public async Task UpdateWorkByPatch()
    {
        //Arrange
        await using var context = new MockDB().CreateDbContext();

        context.Works.Add(new Work
        {
            Name = &quot;Exiting test name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        });

        await context.SaveChangesAsync();

        var updatedWork = new WorkDto
        {
            Name = &quot;Updated test name&quot;,
            TimeStart = &quot;Updated Test TimeStart&quot;,
            TimeEnd = &quot;Updated Test TimeEnd&quot;,
            IsComplete = true
        };

        //Act
        var result = await WorkEndpointV1.UpdateWorkByIdWithPatch(1, updatedWork, context);

        //Assert
        Assert.IsType&lt;NoContent&gt;(result);

        var noContentResult = (NoContent)result;

        Assert.NotNull(noContentResult);

        var workInDb = await context.Works.FindAsync(1);

        Assert.NotNull(workInDb);
        Assert.Equal(&quot;Updated test name&quot;, workInDb!.Name);
        Assert.True(workInDb.IsComplete);
    }

    &#x5B;Fact]
    public async Task DeleteWork()
    {
        //Arrange
        await using var context = new MockDB().CreateDbContext();

        var existingWork = new Work     
        {
            Id = 1,
            Name = &quot;Existing test name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        };

        context.Works.Add(existingWork);

        await context.SaveChangesAsync();

        //Act
        var result = await WorkEndpointV1.DeleteWorkById(existingWork.Id, context);

        //Assert
        Assert.IsType&lt;Results&lt;NoContent, NotFound&gt;&gt;(result);

        var noContentResult = (NoContent)result.Result;

        Assert.NotNull(noContentResult);
        Assert.Empty(context.Works);
    }
}
</pre></div>


<div class="note">Explanation</div>
<p>The above code contains unit test cases written in xUnit that test all the Minimal API endpoints. This includes creation of a new record, reading a record by its id, update a record and delete a record. Test cases when record is not found are also included.</p>
<p>In ASP.NET Core Minimal APIs, <b>TypedResults are preferred over Results</b> because they provide stronger typing, better testability, and automatic API documentation support. While both classes are used to return HTTP responses from API endpoints, <span class="code">TypedResults</span> return specific response types (such as Ok<T>, NotFound, or Created<T>), making the endpoint&#8217;s behavior explicit and easier to validate.</p>
<p>One of the main advantages of using <span class="code">TypedResults</span> is improved testability. Since each response has a concrete type, unit tests can directly verify the returned result without relying on generic interfaces or casting. For example, a test can assert that an endpoint returns <span class="code">TypedResults.Ok&#038;RltWork></span> or TypedResults.NotFound, making the tests more readable, type-safe, and less prone to runtime errors.</p>
<p>See the method <span class="term">GetWorkById</span> that uses <span class="code">TypedResults</span> to return <span class="code">Ok(work)</span> when record is found and <span class="code">NotFound()</span> when record is not available in the database.</p>



<pre class="wp-block-code"><code>public static async Task&lt;Results&lt;Ok&lt;Work>, NotFound>> GetWorkById(int id, WorkDb db)
{
    return await db.Works.FindAsync(id)
        is Work work
            ? TypedResults.Ok(work)
            : TypedResults.NotFound();
}</code></pre>



<p>In it&#8217;s Unit Test case given below we assert both OK and NotFound very easily since we used the return type of <code>Results&lt;Ok<Work>, NotFound></code>.</p>



<pre class="wp-block-code"><code>&#91;Fact]
public async Task GetWorkByIdIfNotExists()
{
    // Arrange
    await using var context = new MockDB().CreateDbContext();

    // Act
    var result = await WorkEndpointV1.GetWorkById(1, context);

    Assert.IsType&lt;Results&lt;Ok&lt;Work>, NotFound>>(result);

    var notFoundResult = (NotFound)result.Result;

    Assert.NotNull(notFoundResult);
}</code></pre>



<p>The following code uses the Ok class, and the value&#8217;s type is a collection of work.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;Fact]
public async Task GetWorkById()
{
    // Arrange
    await using var context = new MockDB().CreateDbContext();

    context.Works.Add(new Work
    {
        Name = &quot;Test Name&quot;,
        TimeStart = &quot;Test TimeStart&quot;,
        TimeEnd = &quot;Test TimeEnd&quot;,
        IsComplete = false
    });

    await context.SaveChangesAsync();

    // Act
    var result = await WorkEndpointV1.GetWorkById(1, context);

    //Assert
    Assert.IsType&lt;Results&lt;Ok&lt;Work&gt;, NotFound&gt;&gt;(result);

    var okResult = (Ok&lt;Work&gt;)result.Result;

    Assert.NotNull(okResult.Value);
    Assert.Equal(1, okResult.Value.Id);
}

&#x5B;Fact]
public async Task CreateWork()
{
    //Arrange
    await using var context = new MockDB().CreateDbContext();

    var newWork = new Work
    {
        Name = &quot;Test Name&quot;,
        TimeStart = &quot;Test TimeStart&quot;,
        TimeEnd = &quot;Test TimeEnd&quot;,
        IsComplete = false
    };

    //Act
    var result = await WorkEndpointV1.CreateWork(newWork, context);

    //Assert
    Assert.IsType&lt;Created&lt;Work&gt;&gt;(result);

    Assert.NotNull(result);
    Assert.NotNull(result.Location);

    Assert.NotEmpty(context.Works);
    Assert.Collection(context.Works, work =&gt;
    {
        Assert.Equal(1, work.Id);
        Assert.Equal(&quot;Test Name&quot;, work.Name);
        Assert.Equal(&quot;Test TimeStart&quot;, work.TimeStart);
        Assert.Equal(&quot;Test TimeEnd&quot;, work.TimeEnd);
        Assert.False(work.IsComplete);
    });
}

&#x5B;Fact]
public async Task UpdateWork()
{
    //Arrange
    await using var context = new MockDB().CreateDbContext();

    context.Works.Add(new Work
    {
        Name = &quot;Exiting test name&quot;,
        TimeStart = &quot;Test TimeStart&quot;,
        TimeEnd = &quot;Test TimeEnd&quot;,
        IsComplete = false
    });

    await context.SaveChangesAsync();

    var updatedWork = new Work
    {
        Name = &quot;Updated test name&quot;,
        TimeStart = &quot;Updated Test TimeStart&quot;,
        TimeEnd = &quot;Updated Test TimeEnd&quot;,
        IsComplete = true
    };

    //Act
    var result = await WorkEndpointV1.UpdateWorkById(1, updatedWork, context);

    //Assert
    Assert.IsType&lt;Results&lt;NoContent, NotFound&gt;&gt;(result);

    var noContentResult = (NoContent)result.Result;

    Assert.NotNull(noContentResult);

    var workInDb = await context.Works.FindAsync(1);

    Assert.NotNull(workInDb);
    Assert.Equal(&quot;Updated test name&quot;, workInDb!.Name);
    Assert.True(workInDb.IsComplete);
}
</pre></div>


<h3>Unit Testing using fakes or mock objects</h3>
<p>Unit tests should verify the behavior of a single component without depending on external resources such as databases, web services, or file systems. To achieve this isolation, developers use fake or mock objects that replace the real dependencies of the component under test. This approach ensures that the test focuses only on the business logic and produces fast, reliable, and repeatable results.</p>
<p>A fake object is a simplified implementation of a dependency that provides predefined behavior. It is often created manually and is suitable for simple testing scenarios. For example, a fake repository can return hard-coded data instead of retrieving records from a database. While fake objects are easy to understand, they can become difficult to maintain as the number of test cases increases.</p>
<p>A mock object, on the other hand, is created using a mocking framework such as Moq, which is one of the most widely used mocking libraries for .NET applications. Moq allows developers to create mock implementations of interfaces or virtual classes dynamically, eliminating the need to write custom fake classes. Developers can configure mock objects to return specific values, throw exceptions, or verify that certain methods are called with the expected parameters.</p>
<p>In ASP.NET Core applications, Moq is commonly used to mock services, repositories, and other dependencies that are injected through the built-in Dependency Injection (DI) container. During unit testing, the real dependency is replaced with a mock object, allowing the component to be tested independently of external systems. This isolation makes tests deterministic and prevents failures caused by unavailable databases or network services.</p>
<p>One of the key advantages of Moq is its ability to verify interactions between objects. Besides returning predefined results, Moq can confirm that a method was invoked the expected number of times and with the correct arguments. This feature helps ensure that the component under test not only produces the correct output but also interacts correctly with its dependencies.</p>
<p>Using fake or mock objects with the Moq package provides several benefits, including faster test execution, better isolation of business logic, improved reliability, easier maintenance, and greater confidence during code refactoring. As a result, Moq has become a standard tool for writing effective unit tests in ASP.NET Core applications, enabling developers to build high-quality and maintainable software.</p>
<p>Next, add a new class called <span class="term">WorkUnitTestMoq.cs</span>. This class contains test cases for testing with Moq objects. Recall the static class called <span class="term">WorkEndpointV2</span> contains the custom extension method called <span class="code">WorkAPIV2()</span>. These contains API method that uses WorkService. We will fake this WorkService using Moq objects.</p>
<p>See it&#8217;s code below</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class WorkUnitTestMoq
{
    &#x5B;Fact]
    public async Task GetWorkByIdIfNotExists()
    {
        // Arrange
        var mock = new Mock&lt;IWorkService&gt;();

        mock.Setup(m =&gt; m.Find(It.Is&lt;int&gt;(id =&gt; id == 1))).ReturnsAsync((Work?)null);

        // Act
        var result = await WorkEndpointV2.GetWorkById(1, mock.Object);

        //Assert
        Assert.IsType&lt;Results&lt;Ok&lt;Work&gt;, NotFound&gt;&gt;(result);

        var notFoundResult = (NotFound)result.Result;

        Assert.NotNull(notFoundResult);
    }

    &#x5B;Fact]
    public async Task GetAllWork()
    {
        // Arrange
        var mock = new Mock&lt;IWorkService&gt;();

        mock.Setup(m =&gt; m.GetAll())
        .ReturnsAsync(new List&lt;Work&gt; {
            new Work
            {
                Id = 1,
                Name = &quot;Test Name 1&quot;,
                TimeStart = &quot;Test TimeStart 1&quot;,
                TimeEnd = &quot;Test TimeEnd 1&quot;,
                IsComplete = false
            },
            new Work
            {
                Id = 2,
                Name = &quot;Test Name 2&quot;,
                TimeStart = &quot;Test TimeStart 2&quot;,
                TimeEnd = &quot;Test TimeEnd 2&quot;,
                IsComplete = true
            }
        });

        // Act
        var result = await WorkEndpointV2.GetAllWork(mock.Object);

        //Assert
        Assert.IsType&lt;Ok&lt;List&lt;Work&gt;&gt;&gt;(result);

        Assert.NotNull(result.Value);
        Assert.NotEmpty(result.Value);
        Assert.Collection(result.Value, work1 =&gt;
        {
            Assert.Equal(1, work1.Id);
            Assert.Equal(&quot;Test Name 1&quot;, work1.Name);
            Assert.False(work1.IsComplete);
        }, work2 =&gt;
        {
            Assert.Equal(2, work2.Id);
            Assert.Equal(&quot;Test Name 2&quot;, work2.Name);
            Assert.True(work2.IsComplete);
        });
    }

    &#x5B;Fact]
    public async Task GetCompletedWork()
    {
        // Arrange
        var mock = new Mock&lt;IWorkService&gt;();

        mock.Setup(m =&gt; m.GetCompleteWork())
        .ReturnsAsync(new List&lt;Work&gt; {
            new Work
            {
                Id = 2,
                Name = &quot;Test Name 2&quot;,
                TimeStart = &quot;Test TimeStart 2&quot;,
                TimeEnd = &quot;Test TimeEnd 2&quot;,
                IsComplete = true
            }
        });

        // Act
        var result = await WorkEndpointV2.GetCompletedWork(mock.Object);

        //Assert
        Assert.IsType&lt;Ok&lt;List&lt;Work&gt;&gt;&gt;(result);

        Assert.NotNull(result.Value);
        Assert.NotEmpty(result.Value);
        Assert.Collection(result.Value, w =&gt;
        {
            Assert.Equal(2, w.Id);
            Assert.Equal(&quot;Test Name 2&quot;, w.Name);
            Assert.True(w.IsComplete);
        });
    }

    &#x5B;Fact]
    public async Task GetWorkById()
    {
        // Arrange
        var mock = new Mock&lt;IWorkService&gt;();

        mock.Setup(m =&gt; m.Find(It.Is&lt;int&gt;(id =&gt; id == 1)))
        .ReturnsAsync(new Work
        {
            Id = 1,
            Name = &quot;Test Name 1&quot;,
            TimeStart = &quot;Test TimeStart 1&quot;,
            TimeEnd = &quot;Test TimeEnd 1&quot;,
            IsComplete = false
        });

        // Act
        var result = await WorkEndpointV2.GetWorkById(1, mock.Object);

        //Assert
        Assert.IsType&lt;Results&lt;Ok&lt;Work&gt;, NotFound&gt;&gt;(result);

        var okResult = (Ok&lt;Work&gt;)result.Result;

        Assert.NotNull(okResult.Value);
        Assert.Equal(1, okResult.Value.Id);
    }

    &#x5B;Fact]
    public async Task CreateWork()
    {
        //Arrange
        var works = new List&lt;Work&gt;();

        var mock = new Mock&lt;IWorkService&gt;();

        var newWork = new Work
        {
            Id = 1,
            Name = &quot;Test Name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        };

        mock.Setup(m =&gt; m.Add(It.Is&lt;Work&gt;(t =&gt; t.Name == newWork.Name &&amp; t.TimeStart == newWork.TimeStart &&amp; t.TimeEnd == newWork.TimeEnd &&amp; t.IsComplete == newWork.IsComplete)))
        .Callback&lt;Work&gt;(a =&gt; works.Add(a))
        .Returns(Task.CompletedTask);

        //Act
        var result = await WorkEndpointV2.CreateWork(newWork, mock.Object);

        //Assert
        Assert.IsType&lt;Created&lt;Work&gt;&gt;(result);

        Assert.NotNull(result);
        Assert.NotNull(result.Location);

        Assert.NotEmpty(works);
        Assert.Collection(works, work =&gt;
        {
            Assert.Equal(1, work.Id);
            Assert.Equal(&quot;Test Name&quot;, work.Name);
            Assert.Equal(&quot;Test TimeStart&quot;, work.TimeStart);
            Assert.Equal(&quot;Test TimeEnd&quot;, work.TimeEnd);
            Assert.False(work.IsComplete);
        });
    }

    &#x5B;Fact]
    public async Task UpdateWork()
    {
        //Arrange
        var mock = new Mock&lt;IWorkService&gt;();

        var existingWork = new Work
        {
            Name = &quot;Exiting test name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        };

        var updatedWork = new Work
        {
            Name = &quot;Updated test name&quot;,
            TimeStart = &quot;Updated Test TimeStart&quot;,
            TimeEnd = &quot;Updated Test TimeEnd&quot;,
            IsComplete = true
        };

        mock.Setup(m =&gt; m.Find(It.Is&lt;int&gt;(id =&gt; id == 1)))
        .ReturnsAsync(existingWork);

        mock.Setup(m =&gt; m.Update(It.Is&lt;Work&gt;(t =&gt; t.Name == updatedWork.Name &&amp; t.TimeStart == updatedWork.TimeStart &&amp; t.TimeEnd == updatedWork.TimeEnd &&amp; t.IsComplete == updatedWork.IsComplete)))
            .Callback&lt;Work&gt;(w =&gt; updatedWork = w)
            .Returns(Task.CompletedTask);

        //Act
        var result = await WorkEndpointV2.UpdateWorkById(1, updatedWork, mock.Object);

        //Assert
        Assert.IsType&lt;Results&lt;NoContent, NotFound&gt;&gt;(result);

        var noContentResult = (NoContent)result.Result;

        Assert.NotNull(noContentResult);

        var work = await WorkEndpointV2.GetWorkById(1, mock.Object);
        var okResult = (Ok&lt;Work&gt;)work.Result;

        Assert.NotNull(work);
        Assert.Equal(&quot;Updated test name&quot;, okResult.Value.Name);
        Assert.True(okResult.Value.IsComplete);
    }

    &#x5B;Fact]
    public async Task UpdateWorkByPatch()
    {
        //Arrange
        var mock = new Mock&lt;IWorkService&gt;();

        var existingWork = new Work
        {
            Id = 1,
            Name = &quot;Exiting test name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        };

        var updatedWork = new WorkDto
        {
            Name = &quot;Updated test name&quot;,
            TimeStart = &quot;Updated Test TimeStart&quot;,
            TimeEnd = &quot;Updated Test TimeEnd&quot;,
            IsComplete = true
        };

        mock.Setup(m =&gt; m.Find(It.Is&lt;int&gt;(id =&gt; id == 1)))
        .ReturnsAsync(existingWork);

        mock.Setup(m =&gt; m.UpdatePatch(It.Is&lt;Work&gt;(t =&gt;
                t.Name == updatedWork.Name &&amp;
                t.TimeStart == updatedWork.TimeStart &&amp;
                t.TimeEnd == updatedWork.TimeEnd &&amp;
                t.IsComplete == updatedWork.IsComplete)))
                .Callback&lt;Work&gt;(w =&gt;
                {
                    // copy fields from Work to WorkDto for later assertions
                    updatedWork.Name = w.Name;
                    updatedWork.TimeStart = w.TimeStart;
                    updatedWork.TimeEnd = w.TimeEnd;
                    updatedWork.IsComplete = w.IsComplete;
                })
                .Returns(Task.CompletedTask);

        //Act
        var result = await WorkEndpointV2.UpdateWorkByIdWithPatch(1, updatedWork, mock.Object);

        //Assert
        Assert.IsType&lt;NoContent&gt;(result);

        var noContentResult = (NoContent)result;

        Assert.NotNull(noContentResult);

        var work = await WorkEndpointV2.GetWorkById(1, mock.Object);
        var okResult = (Ok&lt;Work&gt;)work.Result;

        Assert.NotNull(work);
        Assert.Equal(&quot;Updated test name&quot;, okResult.Value.Name);
        Assert.True(okResult.Value.IsComplete);
    }

    &#x5B;Fact]
    public async Task DeleteWork()
    {
        //Arrange
        var mock = new Mock&lt;IWorkService&gt;();

        var existingWork = new Work
        {
            Id = 1,
            Name = &quot;Existing test name&quot;,
            TimeStart = &quot;Test TimeStart&quot;,
            TimeEnd = &quot;Test TimeEnd&quot;,
            IsComplete = false
        };

        var works = new List&lt;Work&gt; { existingWork };

        mock.Setup(m =&gt; m.Find(It.Is&lt;int&gt;(id =&gt; id == existingWork.Id)))
        .ReturnsAsync(existingWork);

        mock.Setup(m =&gt; m.Remove(It.Is&lt;Work&gt;(t =&gt; t.Id == 1)))
            .Callback&lt;Work&gt;(t =&gt; works.Remove(t))
            .Returns(Task.CompletedTask);

        //Act
        var result = await WorkEndpointV2.DeleteWorkById(existingWork.Id, mock.Object);

        //Assert
        Assert.IsType&lt;Results&lt;NoContent, NotFound&gt;&gt;(result);

        var noContentResult = (NoContent)result.Result;

        Assert.NotNull(noContentResult);
        Assert.Empty(works);
    }
}
</pre></div>


<div class="note">Explanation:</div>
<p>In place of WorkService object we fake it with a Moq object:</p>



<pre class="wp-block-code"><code>var mock = new Mock&lt;IWorkService>();</code></pre>



<p>We then use this fake object in test cases like:</p>



<pre class="wp-block-code"><code>mock.Setup(m => m.Find(It.Is&lt;int>(id => id == 1))).ReturnsAsync((Work?)null);
var result = await WorkEndpointV2.GetWorkById(1, mock.Object);</code></pre>



<p><span class="code">It.Is<int>(id => id == 1)</span> is an argument matcher that tells Moq to apply this configuration only when the Find() method is called with the value 1.</p>
<p>See the code of <span class="code">GetAllWork()</span> test case where Moq object returns 2 work records whenever the WorkService needs to be called.</p>



<pre class="wp-block-code"><code>var mock = new Mock&lt;IWorkService>();

mock.Setup(m => m.GetAll())
.ReturnsAsync(new List&lt;Work> {
    new Work
    {
        Id = 1,
        Name = "Test Name 1",
        TimeStart = "Test TimeStart 1",
        TimeEnd = "Test TimeEnd 1",
        IsComplete = false
    },
    new Work
    {
        Id = 2,
        Name = "Test Name 2",
        TimeStart = "Test TimeStart 2",
        TimeEnd = "Test TimeEnd 2",
        IsComplete = true
    }
});

var result = await WorkEndpointV2.GetAllWork(mock.Object);</code></pre>



<p>In the Test Explorer run all the test and they all passed successfully. See it&#8217;s image below.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/unit-test-minimal-api-passed.png" alt="Minimal API Unit Test passed" title="Minimal API Unit Test passed" class="img-fluid"></p>



<h2>Integration Tests in ASP.NET Core Minimal API</h2>
<p>Integration tests evaluate how different components of an application work together, providing broader coverage than unit tests. While unit tests focus on isolated pieces of code—such as individual methods or classes—integration tests verify that multiple components interact correctly to produce the expected outcome. In some cases, they test the complete workflow required to process a request from start to finish.</p>
<p>Because of their broader scope, integration tests validate the application&#8217;s infrastructure and overall framework. They often involve real components such as:</p>
<ul>
<li>Databases</li>
<li>File systems</li>
<li>Network services</li>
<li>The request-response pipeline</li>
</ul>
<p>Integration tests:</p>
<ol>
<li>Use the same components that the application relies on in production.</li>
<li>Require more code execution and data processing.</li>
<li>Take longer to run due to their broader scope.</li>
</ol>
<p>For this reason, integration tests should be reserved for the most critical infrastructure scenarios. Whenever a behavior can be validated with either a unit test or an integration test, prefer a unit test because it is faster, simpler, and more focused.</p>
<div class="note">Requirements for Integration Tests</div>
<p>Integration testing in ASP.NET Core requires the following components:</p>
<ul>
<li>A test project that contains and executes the integration tests. This project references the System Under Test (SUT).</li>
<li>A test web host and test server client that host the SUT and send HTTP requests while receiving responses.</li>
<li>A test runner to execute the tests and report the results.</li>
</ul>
<div class="note">Integration Test Workflow</div>
<p>Integration tests typically follow the standard Arrange, Act, Assert (AAA) pattern:</p>
<ol>
<li>Configure the SUT&#8217;s test web host.</li>
<li>Create a test server client to communicate with the application.</li>
<li>Arrange: Prepare the test data and HTTP request.</li>
<li>Act: Send the request and capture the response.</li>
<li>Assert: Verify that the actual response matches the expected result.</li>
<li>Repeat the process until all tests have been executed.</li>
<li>Generate and report the test results.</li>
</ol>
<div class="note">Test Host Configuration</div>
<p>The test web host is usually configured differently from the application&#8217;s production host. For example, integration tests often use a separate database, custom configuration settings, or mocked external services to provide a controlled testing environment.</p>
<div class="note">Microsoft.AspNetCore.Mvc.Testing</div>
<p>The <span class="term">Microsoft.AspNetCore.Mvc.Testing</span> package simplifies integration testing by providing the infrastructure needed to host and test an ASP.NET Core application. It manages the test web host and the in-memory TestServer, reducing the amount of setup code required.</p>
<p>Key features of the package include:</p>
<ul>
<li>Copies the SUT&#8217;s dependency (.deps) file to the test project&#8217;s output directory.</li>
<li>Sets the content root to the SUT&#8217;s project directory so that static files, Razor pages, and views are available during testing.</li>
<li>Provides the WebApplicationFactory class, which simplifies bootstrapping the application with TestServer.</li>
</ul>
<h3>Creating a new Project for Integration Test</h3>
<p>We create a new Class Project called <span class="term">IntegrationTest</span>. Here we will write our Integration Tests for the Minimal API. First of all add the following 4 packages to this project:</p>
<ul>
<li>Microsoft.AspNetCore.Mvc.Testing</li>
<li>xunit.v3</li>
<li>xunit.runner.visualstudio</li>
<li>Microsoft.NET.Test.Sdk</li>
<li>Microsoft.EntityFrameworkCore.Sqlite</li>
</ul>
<p>Since the integration tests will use SQLite database so we have added <span class="term">Microsoft.EntityFrameworkCore.Sqlite</span> package which is SQLite database provider for Entity Framework Core.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/integration-test-packages.png" alt="Integration Test Packages" title="Integration Test Packages" class="img-fluid"></p>
<p>Create a new folder called &#8220;Helpers&#8221;, to this folder a new class called <span class="term">TestWebApplicationFactory.cs</span>. This class is a custom test host factory for ASP.NET Core integration tests. Its primary job is to replace the application&#8217;s normal database configuration with a test database and ensure that database starts in a clean state before tests run.</p>



<pre class="wp-block-code"><code>public class TestWebApplicationFactory&lt;TProgram>
    : WebApplicationFactory&lt;TProgram> where TProgram : class
{
    protected override IHost CreateHost(IHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            services.RemoveAll&lt;IDbContextOptionsConfiguration&lt;WorkDb>>();

            // Register WorkDb for tests using Sqlite. Do not call UseInternalServiceProvider; let EF manage its service provider.
            services.AddDbContext&lt;WorkDb>((serviceProvider, options) =>
            {
                var path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                options.UseSqlite($"Data Source={Path.Join(path, "Integration_tests.db")}");
            });

            // Build the provider
            var serviceProvider = services.BuildServiceProvider();

            // Create a scope
            using var scope = serviceProvider.CreateScope();

            var context = scope.ServiceProvider.GetRequiredService&lt;WorkDb>();
            context.Database.EnsureDeleted(); // Ensure a clean database for each test run
            context.Database.EnsureCreated(); // Ensure the database is created
        });

        return base.CreateHost(builder);
    }
}</code></pre>



<p><span class="code">TestWebApplicationFactory<TProgram></span> is a custom integration test factory that extends <span class="code">WebApplicationFactory<TProgram></span> to configure the application specifically for testing. When the test host is created, it replaces the application&#8217;s existing WorkDb database configuration with a SQLite database stored in a local file, preventing tests from using the production database. It then builds a temporary service provider to obtain a WorkDb instance, deletes any existing test database, and recreates it to ensure a clean, consistent database state before the application starts. This allows integration tests to run against an isolated database with a predictable schema and no leftover data from previous test runs.</p>
<p>Notice it remove the existing database configuration.</p>



<pre class="wp-block-code"><code>services.RemoveAll&lt;IDbContextOptionsConfiguration&lt;WorkDb>>();</code></pre>



<p>Then registers a SQLite test database.</p>



<pre class="wp-block-code"><code>services.AddDbContext&lt;WorkDb>((serviceProvider, options) =>
{
    var path = Environment.GetFolderPath(
        Environment.SpecialFolder.LocalApplicationData);

    options.UseSqlite(
        $"Data Source={Path.Join(path, "Integration_tests.db")}");
});</code></pre>



<p>Deletes and then recreates a fresh database copy.</p>



<pre class="wp-block-code"><code>context.Database.EnsureDeleted();
context.Database.EnsureCreated();</code></pre>



<div class="note">Overall flow</div>



<pre class="wp-block-code"><code>Test starts
      │
      ▼
CreateHost()
      │
      ▼
Remove production WorkDb configuration
      │
      ▼
Register SQLite WorkDb
      │
      ▼
Delete Integration_tests.db
      │
      ▼
Create new Integration_tests.db
      │
      ▼
Build ASP.NET Core application
      │
      ▼
Tests run against clean SQLite database</code></pre>



<Next, create a new class called <span class="term">WorkEndpointsV1Tests.cs</span> containing the 2 integration tests.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;Collection(&quot;Sequential&quot;)]
public class WorkEndpointsV1Tests : IClassFixture&lt;TestWebApplicationFactory&lt;Program&gt;&gt;
{
    private readonly TestWebApplicationFactory&lt;Program&gt; _factory;
    private readonly HttpClient _httpClient;

    public WorkEndpointsV1Tests(TestWebApplicationFactory&lt;Program&gt; factory)
    {
        _factory = factory;
        _httpClient = factory.CreateClient();
    }

    public static IEnumerable&lt;object&#x5B;]&gt; InvalidWorks =&gt; new List&lt;object&#x5B;]&gt;
    {
        new object&#x5B;] { new WorkDto { Name = &quot;&quot;, TimeStart = &quot;Test Time Start&quot;, TimeEnd = &quot;Test Time End&quot;, IsComplete = false }, &quot;Name is empty&quot; },
        new object&#x5B;] { new WorkDto { Name = &quot;no&quot;, TimeStart = &quot;Test Time Start&quot;, TimeEnd = &quot;Test Time End&quot;, IsComplete = false }, &quot;Name length &lt; 3&quot; }
    };

    &#x5B;Theory]
    &#x5B;MemberData(nameof(InvalidWorks))]
    public async Task PostWorkWithValidationProblems(WorkDto work, string errorMessage)
    {
        var response = await _httpClient.PostAsJsonAsync(&quot;/works/v1&quot;, work);

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);

        var problemResult = await response.Content.ReadFromJsonAsync&lt;HttpValidationProblemDetails&gt;();

        Assert.NotNull(problemResult?.Errors);
        Assert.Collection(problemResult.Errors, (error) =&gt; Assert.Equal(errorMessage, error.Value.First()));
    }

    &#x5B;Fact]
    public async Task PostWorkWithValidParameters()
    {
        using (var scope = _factory.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetService&lt;WorkDb&gt;();
        }

        var response = await _httpClient.PostAsJsonAsync(&quot;/works/v1&quot;, new WorkDto
        {
            Name = &quot;Test Name&quot;,
            TimeStart = &quot;Test Time Start&quot;,
            TimeEnd = &quot;Test Time End&quot;,
            IsComplete = false
        });

        Assert.Equal(HttpStatusCode.Created, response.StatusCode);

        var works = await _httpClient.GetFromJsonAsync&lt;List&lt;Work&gt;&gt;(&quot;/works/v1&quot;);

        Assert.NotNull(works);
        Assert.Single(works);

        Assert.Collection(works, (work) =&gt;
        {
            Assert.Equal(&quot;Test Name&quot;, work.Name);
            Assert.Equal(&quot;Test Time Start&quot;, work.TimeStart);
            Assert.Equal(&quot;Test Time End&quot;, work.TimeEnd);
            Assert.False(work.IsComplete);
        });
    }
}
</pre></div>


<p><span class="term">WorkEndpointsV1Tests</span> is an integration test class that verifies the behavior of the version 1 Work API endpoints by interacting with a running instance of the application created by <span class="code">TestWebApplicationFactory<Program></span>. The class uses xUnit&#8217;s <span class="term">IClassFixture</span> to share a single test application instance and HttpClient to send HTTP requests to the API, closely simulating real client interactions. The [Collection(&#8220;Sequential&#8221;)] attribute ensures that the tests execute sequentially, preventing conflicts caused by sharing the same SQLite test database. The test suite validates both successful and unsuccessful API behavior: parameterized tests ([Theory]) verify that invalid input data returns a 400 Bad Request response with the expected validation errors, while functional tests (<span class="term">[Fact]</span>) confirm that valid requests create new work items, return a 201 Created response, and persist the data correctly by retrieving and verifying it through the API. This approach ensures that endpoint routing, request validation, database interaction, and response generation all work together as expected in an end-to-end testing environment.</p>
<p>We also add another class called <span class="term">WorkEndpointsV2Tests.cs</span> whose work is to test the version 2 Work API endpoints. This class is very similar to the previous class. It&#8217;s code is given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;Collection(&quot;Sequential&quot;)]
public class WorkEndpointsV2Tests : IClassFixture&lt;TestWebApplicationFactory&lt;Program&gt;&gt;
{
    private readonly TestWebApplicationFactory&lt;Program&gt; _factory;
    private readonly HttpClient _httpClient;

    public WorkEndpointsV2Tests(TestWebApplicationFactory&lt;Program&gt; factory)
    {
        _factory = factory;
        _httpClient = factory.CreateClient();
    }

    public static IEnumerable&lt;object&#x5B;]&gt; InvalidWorks =&gt; new List&lt;object&#x5B;]&gt;
    {
        new object&#x5B;] { new WorkDto { Name = &quot;&quot;, TimeStart = &quot;Test Time Start&quot;, TimeEnd = &quot;Test Time End&quot;, IsComplete = false }, &quot;Name is empty&quot; },
        new object&#x5B;] { new WorkDto { Name = &quot;no&quot;, TimeStart = &quot;Test Time Start&quot;, TimeEnd = &quot;Test Time End&quot;, IsComplete = false }, &quot;Name length &lt; 3&quot; }
    };

    &#x5B;Theory]
    &#x5B;MemberData(nameof(InvalidWorks))]
    public async Task PostWorkWithValidationProblems(WorkDto work, string errorMessage)
    {
        var response = await _httpClient.PostAsJsonAsync(&quot;/works/v2&quot;, work);

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);

        var problemResult = await response.Content.ReadFromJsonAsync&lt;HttpValidationProblemDetails&gt;();

        Assert.NotNull(problemResult?.Errors);
        Assert.Collection(problemResult.Errors, (error) =&gt; Assert.Equal(errorMessage, error.Value.First()));
    }

    &#x5B;Fact]
    public async Task PostWorkWithValidParameters()
    {
        using (var scope = _factory.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetService&lt;WorkDb&gt;();
        }

        var response = await _httpClient.PostAsJsonAsync(&quot;/works/v2&quot;, new WorkDto
        {
            Name = &quot;Test Name&quot;,
            TimeStart = &quot;Test Time Start&quot;,
            TimeEnd = &quot;Test Time End&quot;,
            IsComplete = false
        });

        Assert.Equal(HttpStatusCode.Created, response.StatusCode);

        var works = await _httpClient.GetFromJsonAsync&lt;List&lt;Work&gt;&gt;(&quot;/works/v2&quot;);

        Assert.NotNull(works);
        Assert.Single(works);

        Assert.Collection(works, (work) =&gt;
        {
            Assert.Equal(&quot;Test Name&quot;, work.Name);
            Assert.Equal(&quot;Test Time Start&quot;, work.TimeStart);
            Assert.Equal(&quot;Test Time End&quot;, work.TimeEnd);
            Assert.False(work.IsComplete);
        });
    }
}
</pre></div>


<p>It&#8217;s time we run the Integration tests in the Test Explorer and congrats they all pass. Check the below image.</p>
<img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/08/integration-tests-successful.png" alt="Integration Tests Successful" title="Integration Tests Successful" class="img-fluid"></p>
<p>You can download the source codes by clicking the button:</p>
<p><a class="testLink" href="https://www.yogihosting.com/wp-content/themes/yogi-yogihosting/download/aspnetcore/MinimalAPITesting.zip" target="_blank" rel="noopener">Download</a></p>
<div class="note">Organizing Test Projects</div>
<p>Keep unit tests and integration tests in separate projects. This separation offers several benefits:</p>
<ul>
<li>Prevents integration testing infrastructure from being included in unit test projects.</li>
<li>Makes it easier to run unit tests or integration tests independently.</li>
<li>Improves project organization and maintainability.</li>
</ul>
<div class="note">Conclusion</div>
<p>In this tutorial we learned how to perform Unit and Integration test for ASP.NET Core Minimal APIs. We covered almost all aspects of testing and hope our efforts are satisfactory. If you have any questions then feel free to use the comment&#8217;s section below.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-testing/">How to perform Unit and Integration Testing in Minimal APIs</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-minimal-api-testing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Filters in ASP.NET Core Minimal API</title>
		<link>https://www.yogihosting.com/aspnet-core-minimal-api-filters/</link>
					<comments>https://www.yogihosting.com/aspnet-core-minimal-api-filters/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Mon, 03 Aug 2026 04:33:13 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=23059</guid>

					<description><![CDATA[<p>Filters in ASP.NET Core Minimal APIs are components that execute before and/or after a route handler. They allow you to add common functionality such as validation, logging, authentication checks, or modifying requests and responses without repeating code in every endpoint. Uses of Filters: Validate input data Log requests and responses Check authentication or authorization Modify [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-filters/">Filters in ASP.NET Core Minimal API</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Filters in ASP.NET Core Minimal APIs are components that execute before and/or after a route handler. They allow you to add common functionality such as validation, logging, authentication checks, or modifying requests and responses without repeating code in every endpoint.</p>



<span id="more-23059"></span>



<p>Uses of Filters:</p>
<ul>
<li>Validate input data</li>
<li>Log requests and responses</li>
<li>Check authentication or authorization</li>
<li>Modify request or response data</li>
<li>Handle exceptions</li>
<li>Execute common logic for multiple endpoints</li>
</ul>
<p>Execution Flow</p>



<pre class="wp-block-code"><code>Client Request
      │
      ▼
Endpoint Filter (Before)
      │
      ▼
Route Handler
      │
      ▼
Endpoint Filter (After)
      │
      ▼
Client Response</code></pre>



<div id="contentTable">
<div class="title"><p class="left">Page Contents</p><p class="right"><span title="click to toggle"></span></p></div>
<nav>
<ul>
<li><a href="#using">How to use filters in Minimal API</a></li>
<li><a href="#multi">Multiple Endpoint Filters in Minimal API</a></li>
<li><a href="#validation">Validations in Minimal API with Filters</a></li>
<li><a href="#aa">Authentication &#038; Authorization in Minimal API</a></li>
</ul>
</nav>
</div>
<h2 id="using">How to use filters in Minimal API</h2>
<p>We use <span class="term">AddEndpointFilter</span> extension method. To this method we  provide a Delegate that fulfills two core roles:</p>
<ol>
<li>It receives the execution context: EndpointFilterInvocationContext</li>
<li>It returns the next step in the pipeline: EndpointFilterDelegate</li>
</ol>
<p><span class="term">EndpointFilterInvocationContext</span>: Provides direct access to the current request&#8217;s HttpContext and exposes an Arguments list.</p>
<p><span class="term">Arguments List</span>: Contains the arguments passed to the route handler. These arguments are structured in the exact order in which they appear in the handler&#8217;s declaration. A classic example is given on official Microsoft docs, check below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

string ColorName(string color) =&gt; $&quot;Color specified: {color}!&quot;;

app.MapGet(&quot;/colorSelector/{color}&quot;, ColorName)
    .AddEndpointFilter(async (invocationContext, next) =&gt;
    {
        var color = invocationContext.GetArgument&lt;string&gt;(0);

        if (color == &quot;Red&quot;)
        {
            return Results.Problem(&quot;Red not allowed!&quot;);
        }
        return await next(invocationContext);
    });

app.Run();
</pre></div>


<div class="note">Explanation:</div>
<p>Here we have defined the endpoint handler.</p>



<pre class="wp-block-code"><code>string ColorName(string color) => $"Color specified: {color}!";</code></pre>



<p>This is a simple method that accepts a string parameter. If the endpoint executes successfully, it returns:</p>



<pre class="wp-block-code"><code>Color specified: Blue!

or

Color specified: Green!</code></pre>



<p>depending on the URL.</p>
<p>Map the endpoint:</p>



<pre class="wp-block-code"><code>app.MapGet("/colorSelector/{color}", ColorName)</code></pre>



<p>This creates a GET endpoint.</p>
<p>Example URLs:</p>



<pre class="wp-block-code"><code>GET /colorSelector/Blue
GET /colorSelector/Green
GET /colorSelector/Red</code></pre>



<p>The {color} part is a route parameter. For example:</p>



<pre class="wp-block-code"><code>/colorSelector/Blue</code></pre>



<p>binds.</p>



<pre class="wp-block-code"><code>color = "Blue"</code></pre>



<p>and passes it to:</p>



<pre class="wp-block-code"><code>ColorName(color)</code></pre>



<p>Add an Endpoint Filter:</p>



<pre class="wp-block-code"><code>.AddEndpointFilter(async (invocationContext, next) => {}</code></pre>



<p>This attaches a filter only to this endpoint. Think of the execution order like this:</p>



<pre class="wp-block-code"><code>Request
   ↓
Endpoint Filter
   ↓
Endpoint Handler (ColorName)
   ↓
Response</code></pre>



<p>The filter can:</p>
<ul>
<li>inspect arguments</li>
<li>modify arguments</li>
<li>stop execution</li>
<li>modify the response</li>
</ul>
<p>Read the endpoint argument:</p>



<pre class="wp-block-code"><code>var color = invocationContext.GetArgument&lt;string>(0);</code></pre>



<p>invocationContext contains all arguments passed to the endpoint. The endpoint is:</p>



<pre class="wp-block-code"><code>string ColorName(string color)</code></pre>



<p>Its parameters are:</p>



<pre class="wp-block-code"><code>Index 0 → color</code></pre>



<p>So:</p>



<pre class="wp-block-code"><code>GetArgument&lt;string>(0)</code></pre>



<p>returns the route value. If the URL is:</p>



<pre class="wp-block-code"><code>/colorSelector/Blue</code></pre>



<p>then:</p>



<pre class="wp-block-code"><code>color == "Blue"</code></pre>



<p>Validate the value:</p>



<pre class="wp-block-code"><code>if (color == "Red")
{
    return Results.Problem("Red not allowed!");
}</code></pre>



<p>If the client requests:</p>



<pre class="wp-block-code"><code>GET /colorSelector/Red</code></pre>



<p>the filter immediately returns:</p>



<pre class="wp-block-code"><code>Results.Problem(...)</code></pre>



<p>instead of calling the endpoint. The endpoint handler never executes. The client receives a response similar to:</p>



<pre class="wp-block-code"><code>{
    "title": "An error occurred.",
    "detail": "Red not allowed!"
}</code></pre>



<p>Continue to the endpoint:</p>



<pre class="wp-block-code"><code>return await next(invocationContext);</code></pre>



<p>next() calls the next component in the endpoint pipeline. If there are no more filters, it calls:</p>



<pre class="wp-block-code"><code>ColorName(color)</code></pre>



<p>So:</p>



<pre class="wp-block-code"><code>GET /colorSelector/Blue</code></pre>



<p>executes:</p>



<pre class="wp-block-code"><code>ColorName("Blue")</code></pre>



<p>and returns:</p>



<pre class="wp-block-code"><code>Color specified: Blue!</code></pre>



<h3 id="multi">Multiple Endpoint Filters in Minimal API</h3>
<p>We can also add multiple Endpoint Filters to Minimal API. Think of them as layers wrapped around the endpoint, much like nested boxes. Check the below code where you notice that the endpoint itself does almost nothing. The interesting part is the filters attached to it.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet(&quot;/&quot;, () =&gt;
    {
        app.Logger.LogInformation(&quot;             Endpoint&quot;);
        return &quot;Test of multiple filters&quot;;
    })
    .AddEndpointFilter(async (efiContext, next) =&gt;
    {
        app.Logger.LogInformation(&quot;Before 1st filter&quot;);
        var result = await next(efiContext);
        app.Logger.LogInformation(&quot;After 1st filter&quot;);
        return result;
    })
    .AddEndpointFilter(async (efiContext, next) =&gt;
    {
        app.Logger.LogInformation(&quot; Before 2nd filter&quot;);
        var result = await next(efiContext);
        app.Logger.LogInformation(&quot; After 2nd filter&quot;);
        return result;
    })
    .AddEndpointFilter(async (efiContext, next) =&gt;
    {
        app.Logger.LogInformation(&quot;     Before 3rd filter&quot;);
        var result = await next(efiContext);
        app.Logger.LogInformation(&quot;     After 3rd filter&quot;);
        return result;
    });

app.Run();
</pre></div>


<div class="note">Explanation:</div>
<p>Map the endpoint.</p>



<pre class="wp-block-code"><code>app.MapGet("/", () =>
{
    app.Logger.LogInformation("             Endpoint");
    return "Test of multiple filters";
})</code></pre>



<p>This creates a GET endpoint for the root URL (/).</p>
<p>When the endpoint finally executes, it:</p>
<ul>
<li>Writes &#8220;Endpoint&#8221; to the log.</li>
<li>Returns the string:</li>
</ul>



<pre class="wp-block-code"><code>Test of multiple filters</code></pre>



<p>First Endpoint Filter:</p>



<pre class="wp-block-code"><code>.AddEndpointFilter(async (efiContext, next) =>
{
    app.Logger.LogInformation("Before 1st filter");

    var result = await next(efiContext);

    app.Logger.LogInformation("After 1st filter");

    return result;
})</code></pre>



<p>This filter runs before the endpoint. Before calling next().</p>



<pre class="wp-block-code"><code>app.Logger.LogInformation("Before 1st filter");</code></pre>



<p>prints:</p>



<pre class="wp-block-code"><code>Before first filter</code></pre>



<p>Call next:</p>



<pre class="wp-block-code"><code>await next(efiContext);</code></pre>



<p>This passes execution to the next filter. After that filter (and eventually the endpoint) finishes, execution comes back here. Then:</p>



<pre class="wp-block-code"><code>app.Logger.LogInformation("After 1st filter");</code></pre>



<p>runs:</p>



<p>Second Endpoint Filter:</p>



<pre class="wp-block-code"><code>.AddEndpointFilter(async (efiContext, next) =>
{
    app.Logger.LogInformation(" Before 2nd filter");

    var result = await next(efiContext);

    app.Logger.LogInformation(" After 2nd filter");

    return result;
})</code></pre>



<p>This behaves exactly like the first filter. It surrounds everything after it.</p>
<p>Third Endpoint Filter:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
.AddEndpointFilter(async (efiContext, next) =&gt;
{
    app.Logger.LogInformation(&quot;     Before 3rd filter&quot;);

    var result = await next(efiContext);

    app.Logger.LogInformation(&quot;     After 3rd filter&quot;);

    return result;
});
</pre></div>


<p>This is the last filter.</p>
<p>Calling:</p>



<pre class="wp-block-code"><code>await next(efiContext);</code></pre>



<p>doesn&#8217;t invoke another filter because none remain. Instead, it invokes the endpoint.</p>
<p>Execution Order:</p>
<p>Suppose you request:</p>



<pre class="wp-block-code"><code>GET /</code></pre>



<p>Step 1:</p>
<p>The first filter starts.</p>



<pre class="wp-block-code"><code>Before first filter</code></pre>



<p>It calls:</p>



<pre class="wp-block-code"><code>await next()</code></pre>



<p>Step 2:</p>
<p>The second filter starts.</p>



<pre class="wp-block-code"><code>Before 2nd filter</code></pre>



<p>It calls:</p>



<pre class="wp-block-code"><code>await next()</code></pre>



<p>Step 3:</p>
<p>The third filter starts.</p>



<pre class="wp-block-code"><code>Before 3rd filter</code></pre>



<p>It calls:</p>



<pre class="wp-block-code"><code>await next()</code></pre>



<p>Step 4:</p>
<p>No more filters remain, so the endpoint executes.</p>



<pre class="wp-block-code"><code>Endpoint</code></pre>



<p>The endpoint returns:</p>



<pre class="wp-block-code"><code>Test of multiple filters</code></pre>



<p>Step 5:</p>
<p>Execution returns to the third filter.</p>



<pre class="wp-block-code"><code>After 3rd filter</code></pre>



<p>Step 6:</p>
<p>Execution returns to the second filter.</p>



<pre class="wp-block-code"><code>After 2nd filter</code></pre>



<p>Step 7:</p>
<p>Execution returns to the first filter.</p>



<pre class="wp-block-code"><code>After first filter</code></pre>



<p>Final Log Output:</p>
<p>The logs appear in this order:</p>



<pre class="wp-block-code"><code>Before first filter
 Before 2nd filter
     Before 3rd filter
             Endpoint
     After 3rd filter
 After 2nd filter
After first filter</code></pre>



<h2 id="validation">Validations in Minimal API with Filters</h2>
<p>As applications grow, validating incoming requests becomes essential to ensure data integrity, application reliability, and security. One effective way to implement validation in Minimal APIs is by using endpoint filters, which allow developers to intercept requests before they reach the endpoint handler. Endpoint filters enable validation logic to be centralized and reused across multiple endpoints, reducing code duplication and improving maintainability.</p>
<p>The below example performs validation with the help of filters.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapPut(&quot;/works/{id}&quot;, async (int id, Work work, WorkDb db) =&gt;
{
    var todo = await db.Works.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = work.Name;
    todo.TimeStart = work.TimeStart;
    todo.TimeEnd = work.TimeEnd;
    todo.IsComplete = work.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
}).AddEndpointFilter(async (efiContext, next) =&gt;
{
    var w = efiContext.GetArgument&lt;Work&gt;(1);

    var validationError = Utilities.IsValid(w);

    if (!string.IsNullOrEmpty(validationError))
    {
        return Results.Problem(validationError);
    }
    return await next(efiContext);
});
</pre></div>


<p>The Work class code:</p>



<pre class="wp-block-code"><code>public class Work
{
    public int Id { get; set; }
     
    public string Name { get; set; }
 
    public string TimeStart { get; set; }
 
    public string TimeEnd { get; set; }
 
    public bool IsComplete { get; set; }
}</code></pre>



<div class="note">Explaination:</div>
<p>After defining the endpoint, the following code attaches an endpoint filter:</p>



<pre class="wp-block-code"><code>.AddEndpointFilter(async (efiContext, next) =></code></pre>



<p>An endpoint filter executes before and/or after the endpoint handler. It can:</p>
<ul>
<li>validate input</li>
<li>log requests</li>
<li>authorize users</li>
<li>measure execution time</li>
<li>modify responses</li>
</ul>
<p>In this example, it performs validation.</p>
<p>Accessing the Request Object.</p>



<pre class="wp-block-code"><code>var w = efiContext.GetArgument&lt;Work>(1);</code></pre>



<p><span class="term">GetArgument<T>()</span> retrieves one of the endpoint handler&#8217;s arguments. Here it retrieves the Work object, since Work is the second parameter.</p>
<p>Validating the Object:</p>



<pre class="wp-block-code"><code>var validationError = Utilities.IsValid(w);</code></pre>



<p>The Utilities.IsValid() method performs custom validation on the Work object.</p>
<p>For example, it might check that:</p>
<ul>
<li>Name is not empty.</li>
<li>TimeStart is earlier than TimeEnd.</li>
<li>Required fields are present.</li>
</ul>
<p>It returns:</p>
<ul>
<li>an error message if validation fails.</li>
<li>null or an empty string if validation succeeds.</li>
</ul>
<p>Returning Validation Errors:</p>



<pre class="wp-block-code"><code>if (!string.IsNullOrEmpty(validationError))
{
    return Results.Problem(validationError);
}</code></pre>



<p>If validation fails, the filter stops the request and returns an HTTP error response containing the validation message.</p>
<p>For example:</p>



<pre class="wp-block-code"><code>{
    "title": "An error occurred.",
    "detail": "TimeStart must be earlier than TimeEnd."
}</code></pre>



<p>The endpoint handler is not executed when validation fails.</p>
<p>Calling the Endpoint:</p>



<pre class="wp-block-code"><code>return await next(efiContext);</code></pre>



<p>If validation succeeds, the filter calls the next stage in the pipeline, which executes the endpoint handler.</p>
<p>Execution flow:</p>



<pre class="wp-block-code"><code>HTTP Request
      │
      ▼
Endpoint Filter
      │
      ├── Validation fails
      │       │
      │       ▼
      │  Return Problem()
      │
      └── Validation succeeds
              │
              ▼
      Endpoint Handler
              │
              ▼
      Update Database
              │
              ▼
      Return 204 No Content</code></pre>



<p>Benefits of Using an Endpoint Filter for Validation:</p>
<ul>
<li><b>Separation of concerns:</b> Validation logic is kept separate from the endpoint&#8217;s business logic.</li>
<li><b>Code reuse:</b> The same validation filter can be applied to multiple endpoints.</li>
<li><b>Cleaner handlers:</b> Endpoint methods remain focused on processing valid requests.</li>
<li><b>Consistent error handling:</b> All validation failures can return a standardized response format.</li>
<li><b>Improved maintainability:</b> Validation rules can be updated in one place without modifying individual endpoints.</li>
</ul>
<h3>Implementing IEndpointFilter interface</h3>
<p>Besides being defined as delegates, endpoint filters can also be implemented by creating a class that implements the <span class="term">IEndpointFilter</span> interface. This approach encapsulates the filter logic within a reusable class, making it easier to maintain and apply across multiple endpoints. The following code demonstrates the previous validation filter implemented as a class that implements the IEndpointFilter interface:</p>



<pre class="wp-block-code"><code>public class WorkIsValidFilter : IEndpointFilter
{
    private ILogger _logger;

    public WorkIsValidFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger&lt;WorkIsValidFilter>();
    }

    public async ValueTask&lt;object?> InvokeAsync(EndpointFilterInvocationContext efiContext,
        EndpointFilterDelegate next)
    {
        var work = efiContext.GetArgument&lt;Work>(1);

        var validationError = Utilities.IsValid(work!);

        if (!string.IsNullOrEmpty(validationError))
        {
            _logger.LogWarning(validationError);
            return Results.Problem(validationError);
        }
        return await next(efiContext);
    }
}</code></pre>



<p>Filters that implement the <span class="term">IEndpointFilter</span> interface can access services registered in the Dependency Injection (DI) container through constructor injection or service resolution, as demonstrated in the previous example. However, while endpoint filters can use dependencies provided by DI, the filter instances themselves are not resolved directly from the DI container.</p>
<p>The &#8220;WorkIsValidFilter&#8221; is applied to the following endpoints:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapPut(&quot;/works/{id}&quot;, async (int id, Work work, WorkDb db) =&gt;
{
    var todo = await db.Works.FindAsync(id);

    if (todo is null) return Results.NotFound();

    todo.Name = work.Name;
    todo.TimeStart = work.TimeStart;
    todo.TimeEnd = work.TimeEnd;
    todo.IsComplete = work.IsComplete;

    await db.SaveChangesAsync();

    return Results.NoContent();
}).AddEndpointFilter&lt;WorkIsValidFilter&gt;(); 
</pre></div>


<h2 id="aa">Authentication &#038; Authorization in Minimal API</h2>
<p>Authentication verifies the identity of a user before allowing access to an API. Once the user&#8217;s identity is established, authorization determines whether the authenticated user has permission to access specific API resources.</p>
<p>In ASP.NET Core, authorization is handled by the IAuthorizationService, which is registered when you call the AddAuthorization extension method.</p>
<p>In the following example, the /hello endpoint is protected by an authorization policy. To access this endpoint, the authenticated user must satisfy two requirements:</p>
<ol>
<li>Belong to the &#8220;admin&#8221; role.</li>
<li>Have a scope claim with the value &#8220;head&#8221;.</li>
</ol>
<p>Only users who meet both conditions are authorized to access the /hello resource.</p>
<p>The code below creates a new authorization policy named <u>LevelOne</u> that encapsulates two authorization requirements:</p>
<ol>
<li>A role-based requirement via the RequireRole for users with an admin role.</li>
<li>A claim-based requirement via the RequireClaim for which the user must provide a head scope claim.</li>
</ol>
<p>The <u>LevelOne</u> policy is provided as a required policy to the /hello endpoint:</p>



<pre class="wp-block-code"><code>using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorizationBuilder()
  .AddPolicy("LevelOne", policy =>
        policy
            .RequireRole("admin")
            .RequireClaim("scope", "head"));

var app = builder.Build();

app.MapGet("/hello", () => "Hello world!")
  .RequireAuthorization("LevelOne");

app.Run();</code></pre>



<h3>Using an Endpoint Filter for Custom Authorization</h3>
<p>Filters are useful when you need authorization rules that go beyond the built-in policy system. For example, suppose only the owner of a work item may edit it.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class OwnerFilter : IEndpointFilter
{
    public async ValueTask&lt;object?&gt; InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var httpContext = context.HttpContext;

        if (!httpContext.User.Identity!.IsAuthenticated)
        {
            return Results.Unauthorized();
        }

        var userId = httpContext.User.FindFirst(&quot;sub&quot;)?.Value;

        var work = context.GetArgument&lt;Work&gt;(1);

        if (work.OwnerId != userId)
        {
            return Results.Forbid();
        }

        return await next(context);
    }
}
</pre></div>


<p>Apply the filter:</p>



<pre class="wp-block-code"><code>app.MapPut("/works/{id}", UpdateWork)
    .AddEndpointFilter&lt;OwnerFilter>()
    .RequireAuthorization();</code></pre>



<p>Execution Flow:</p>



<pre class="wp-block-code"><code>Client Request
      │
      ▼
Authentication Middleware
      │
      ▼
Authorization Middleware
      │
      ▼
Endpoint Filter (Custom Rule)
      │
      ▼
Endpoint Handler
      │
      ▼
Database</code></pre>



<h3>Best Practice</h3>
<p>Use the built-in authentication and authorization system for securing your Minimal APIs. Endpoint filters should complement this system by implementing application-specific rules, such as verifying resource ownership, checking business constraints, or enforcing custom access requirements. This separation keeps your application secure, maintainable, and aligned with ASP.NET Core best practices.</p>
<div class="note">Conclusion</div>
<p>Endpoint filters are a powerful feature of ASP.NET Core Minimal APIs that provide a clean and reusable way to execute logic before and after an endpoint handler. They help separate cross-cutting concerns, such as validation, logging, authentication, authorization, and exception handling, from the core business logic, resulting in cleaner and more maintainable endpoint implementations.</p> 
<p>By encapsulating common functionality in filters, developers can reduce code duplication, improve consistency across endpoints, and simplify application maintenance. Whether implemented as delegates for simple scenarios or as classes implementing the IEndpointFilter interface for more complex and reusable functionality, endpoint filters enhance the flexibility, readability, and scalability of Minimal API applications, making them an essential tool for building robust and maintainable web APIs.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-filters/">Filters in ASP.NET Core Minimal API</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-minimal-api-filters/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Complete Guide to Minimal API Response</title>
		<link>https://www.yogihosting.com/aspnet-core-minimal-api-response/</link>
					<comments>https://www.yogihosting.com/aspnet-core-minimal-api-response/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Thu, 23 Jul 2026 13:00:13 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22997</guid>

					<description><![CDATA[<p>In ASP.NET Core Minimal APIs, a response represents the data returned by an endpoint to the client after processing an HTTP request. Minimal APIs provide several built-in response types through the Results class, making it easy to return contents such as JSON, plain text, files, streams, redirects, status codes, or custom responses without the overhead [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-response/">Complete Guide to Minimal API Response</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In ASP.NET Core Minimal APIs, a response represents the data returned by an endpoint to the client after processing an HTTP request. Minimal APIs provide several built-in response types through the Results class, making it easy to return contents such as JSON, plain text, files, streams, redirects, status codes, or custom responses without the overhead of MVC controllers. For example, <span class="code">Results.Ok()</span> returns a successful HTTP 200 response with data, <span class="code">Results.NotFound()</span> returns a 404 status code, and <span class="code">Results.Stream()</span> streams data directly to the client. These response helpers produce implementations of <b>IResult</b>, allowing developers to create concise, readable, and efficient APIs while ensuring the correct HTTP status codes, headers, and content types are sent to the client.</p>



<span id="more-22997"></span>



<h2>string return values</h2>
<p>The following endpoints return a Hello world text. The 200 status code is returned with <span class="term">text/plain</span> Content-Type header.</p>



<pre class="wp-block-code"><code>app.MapGet("/hello", () =&gt; "Hello World");
app.MapGet("/hello", () =&gt; Results.Text("Hello World"));</code></pre>



<h2>JSON response</h2>
<p>The below 2 endpoints return a json with value Hello World. The 200 status code is returned with <span class="term">application/json</span> Content-Type header.</p>



<pre class="wp-block-code"><code>app.MapGet("/hello", () =&gt; new { Message = "Hello World" });
app.MapGet("/hello", () =&gt; Results.Json(new { Message = "Hello World" }));</code></pre>



<h2>Custom Status Code</h2>
<p>The endpoint returns a 405 status code.</p>



<pre class="wp-block-code"><code>app.MapGet("/405", () =&gt; Results.StatusCode(405));</code></pre>



<h2>Internal Server Error</h2>
<p>The endpoint returns a 500 status code.</p>



<pre class="wp-block-code"><code>app.MapGet("/500", () =&gt; Results.InternalServerError("Something went wrong!"));</code></pre>



<h2>Adding and Modifying Headers</h2>
<p>Use the <u>HttpResponse</u> object to add or modify response headers:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapGet(&quot;/&quot;, (HttpContext context) =&gt; {
    // Add a custom header
    context.Response.Headers&#x5B;&quot;X-App-Custom-Header&quot;] = &quot;CustomValue&quot;;

    // Modify a header called CacheControl
    context.Response.Headers.CacheControl = $&quot;public,max-age=3600&quot;;

    return &quot;Hello World&quot;;
});
</pre></div>


<h2>Redirect</h2>



<pre class="wp-block-code"><code>app.MapGet("/old-path", () =&gt; Results.Redirect("/new-path"));</code></pre>



<h2>File</h2>



<pre class="wp-block-code"><code>app.MapGet("/download", () =&gt; Results.File("somefile.text"));</code></pre>



<div id="contentTable">
<div class="title"><p class="left">Page Contents</p><p class="right"><span title="click to toggle"></span></p></div>
<nav>
<ul>
<li><a href="#stream">Stream Response in Minimal API</a>
<ul>
<li><a href="#svideo">Stream a video from Minimal API</a></li>
</ul>
</li>
<li><a href="#problemdetails">Returning ProblemDetails response from Minimal API</a></li>
<li><a href="#cproblemdetails">Customize validation error responses using IProblemDetailsService</a></li>
<li><a href="#file">Returning File response from Minimal API</a>
<ul>
<li><a href="#openapi">OpenAPI support for File Response</a></li>
</ul>
</li>
<li><a href="#cache">File Responses for Conditional Requests and Cache Validation</a></li>
<li><a href="#range">File Response for range requests</a></li>
</ul>
</nav>
</div>
<h2 id="stream">Stream Response in Minimal API</h2>
<p>Instead of downloading the entire response into a string or byte array, we can return a Stream connected to the response body. Using a stream is beneficial because:</p>
<ol>
<li>it avoids loading the entire file into memory.</li>
<li>it&#8217;s efficient for large files.</li>
<li>data can begin flowing to the client immediately.</li>
</ol>
<p>For example, if the remote server returns:</p>



<pre class="wp-block-code"><code>&#91;
  {
    "name": "Pikachu",
    "type": "Electric"
  }
]</code></pre>



<p>the stream contains those bytes as they arrive.</p>
<p>Check the below code where stream is the response from a minimal api endpoint.</p>



<pre class="wp-block-code"><code>var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var proxyClient = new HttpClient();
app.MapGet("/product", async () =&gt; 
{
    var stream = await proxyClient.GetStreamAsync("http://flipkart.com/products/saleproducts.json");
    // Proxy the response as JSON
    return Results.Stream(stream, "application/json");
});

app.Run();</code></pre>



<p>In the above code, the last line returns a stream &#8211; <code>return Results.Stream(stream, "application/json");</code>.</p> <p><code>Results.Stream()</code> creates an HTTP response whose body is the provided stream.</p>
<p>The second parameter:</p>



<pre class="wp-block-code"><code>"application/json"</code></pre>



<p>sets the response&#8217;s Content-Type header.</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/json</code></pre>



<p>And this is followed by the JSON from the remote server.</p>
<div class="note">What the endpoint does?</div>
<p>When the browser request:</p>



<pre class="wp-block-code"><code>GET http://localhost:5000/product</code></pre>



<p>The sequence is:</p>



<pre class="wp-block-code"><code>Client
   │
   │ GET /product
   ▼
Your ASP.NET API
   │
   │ GET http://flipkart.com/products/saleproducts.json
   ▼
Flipkart Server
   │
   │ JSON Stream
   ▼
Your API
   │
   │ Streams bytes directly
   ▼
Client</code></pre>



<p>The minimal api does not parse or modify the JSON—it simply forwards it.</p>
<div class="note">Why use Results.Stream?</div>
<p>Without streaming, we might write:</p>



<pre class="wp-block-code"><code>var json = await proxyClient.GetStringAsync("http://flipkart.com/products/saleproducts.json");
return Results.Content(json, "application/json");</code></pre>



<p>This approach:</p>
<ol>
<li>downloads the entire JSON into memory,</li>
<li>creates a large string,</li>
<li>then sends it to the client.</li>
</ol>
<p>With <span class="term">Results.Stream</span>:</p>



<pre class="wp-block-code"><code>var stream = await proxyClient.GetStreamAsync(...);
return Results.Stream(stream, "application/json");</code></pre>



<p>The data is forwarded as it is read from the upstream server, making it more memory-efficient, especially for large responses.</p>
<h3 id="svideo">Stream a video from Minimal API</h3>
<p>Serving large video files can be done by <span class="term">Results.Stream</span>. The below given code streams a video stored in an Azure Blob Storage container to the client.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using Azure.Storage.Blobs;

var builder = WebApplication.CreateBuilder(args);

// Register BlobServiceClient
builder.Services.AddSingleton(_ =&gt;
    new BlobServiceClient(builder.Configuration.GetConnectionString(&quot;AzureBlobStorage&quot;)));

var app = builder.Build();

app.MapGet(&quot;/video/{fileName}&quot;, async (string fileName, BlobServiceClient blobServiceClient) =&gt;
{
    // Get the blob container
    var containerClient = blobServiceClient.GetBlobContainerClient(&quot;videos&quot;);

    // Get the requested blob
    var blobClient = containerClient.GetBlobClient(fileName);

    // Check whether the blob exists
    if (!await blobClient.ExistsAsync())
    {
        return Results.NotFound(&quot;Video not found.&quot;);
    }

    // Open the blob as a stream
    var stream = await blobClient.OpenReadAsync();

    // Stream the video to the client
    return Results.Stream(
        stream,
        contentType: &quot;video/mp4&quot;,
        fileDownloadName: fileName);
});

app.Run();
</pre></div>


<div class="note">How it works:</div>
<ul>
<li>A BlobServiceClient is registered with dependency injection.</li>
<li>The endpoint accepts the video file name as a route parameter.</li>
<li>The API connects to the videos container in Azure Blob Storage.</li>
<li>It checks whether the requested blob exists.</li>
<li>If the blob exists, OpenReadAsync() opens a stream to the blob.</li>
<li>Results.Stream() streams the video directly to the client without loading the entire file into memory.</li>
<li>The response uses the video/mp4 content type so browsers and media players can recognize and play the video.</li>
</ul>



<h2 id="problemdetails">Returning ProblemDetails response from Minimal API</h2>
<p>ProblemDetails is a standardized way for a Web API to return error information to clients. It is based on the IETF standard RFC 7807 (now updated by RFC 9457) and is built into ASP.NET Core.</p>
<p>Instead of returning inconsistent error responses like:</p>



<pre class="wp-block-code"><code>{
  "error": "Something went wrong"
}</code></pre>



<p>You return a structured response like:</p>



<pre class="wp-block-code"><code>{
  "type": "https://example.com/errors/not-found",
  "title": "Resource not found",
  "status": 404,
  "detail": "The product with ID 10 does not exist.",
  "instance": "/api/products/10"
}</code></pre>



<p>Properties of ProblemDetails:</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Property</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>A URI identifying the type of problem. Can point to documentation.</td>
</tr>
<tr>
<td>title</td>
<td>A short, human-readable summary of the error.</td>
</tr>
<tr>
<td>status</td>
<td>The HTTP status code (e.g., 400, 404, 500).</td>
</tr>
<tr>
<td>detail</td>
<td>A detailed explanation of the specific error.</td>
</tr>
<tr>
<td>instance</td>
<td>The URI of the request that caused the error.</td>
</tr>
<tr>
<td>extensions</td>
<td>A dictionary for custom fields (e.g., error code, trace ID).</td>
</tr>
</tbody> 
</table>
</div>
<p><span class="term">IProblemDetailsService</span> is the service responsible for creating and writing ProblemDetails responses in ASP.NET Core. In the Program.cs class,  register the Problem Details service with ASP.NET Core&#8217;s dependency injection container. It enables your application to produce standardized RFC 9457/RFC 7807 error responses (ProblemDetails) for exceptions and HTTP error status codes.</p>



<pre class="wp-block-code"><code>var builder = WebApplication.CreateBuilder(args);

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.MapGet("/employee/{id:int}", (int id) =&gt; id &lt;= 0 ? Results.BadRequest() : Results.Ok(new Employee(id)));

app.Run();</code></pre>



<p><b>What does AddProblemDetails() do?</b> &#8211; It tells ASP.NET Core: &#8220;When an error occurs, generate a standardized ProblemDetails response instead of a plain text or HTML error page.&#8221;</p>
<p>Without AddProblemDetails() &#8211; Suppose an exception occurs by the below code.</p>



<pre class="wp-block-code"><code>app.MapGet("/", () =&gt;
{
    throw new Exception("Database connection failed");
});</code></pre>



<p>The response will be in plain text:</p>



<pre class="wp-block-code"><code>An error occurred while processing your request.</code></pre>



<p>(or an HTML error page (depending on the environment and middleware).)</p>
<p>With AddProblemDetails() &#8211; The same exception can produce a JSON response like:</p>



<pre class="wp-block-code"><code>{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
  "title": "An error occurred while processing your request.",
  "status": 500
}</code></pre>



<p>This format is consistent and easier for API clients to consume.</p>
<div class="note">Example in ASP.NET Core Returning a 404</div>



<pre class="wp-block-code"><code>&#91;HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
    var product = repository.Get(id);

    if (product == null)
    {
        return Problem(
            title: "Product not found",
            detail: $"No product exists with ID {id}.",
            statusCode: StatusCodes.Status404NotFound);
    }

    return Ok(product);
}</code></pre>



<p>The response:</p>



<pre class="wp-block-code"><code>{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
  "title": "Product not found",
  "status": 404,
  "detail": "No product exists with ID 5."
}</code></pre>



<div class="note">Custom ProblemDetails</div>
<p>You can create one manually:</p>



<pre class="wp-block-code"><code>var problem = new ProblemDetails
{
    Title = "Insufficient Balance",
    Detail = "Your account balance is too low.",
    Status = StatusCodes.Status400BadRequest,
    Type = "https://example.com/errors/insufficient-balance",
    Instance = HttpContext.Request.Path
};

problem.Extensions&#91;"errorCode"] = "BAL001";
problem.Extensions&#91;"traceId"] = HttpContext.TraceIdentifier;

return BadRequest(problem);</code></pre>



<p>The response:</p>



<pre class="wp-block-code"><code>{
  "type": "https://example.com/errors/insufficient-balance",
  "title": "Insufficient Balance",
  "status": 400,
  "detail": "Your account balance is too low.",
  "instance": "/api/payments",
  "errorCode": "BAL001",
  "traceId": "00-abc123..."
}</code></pre>



<div class="note">ProblemDetails response with a custom extension</div>
<p>We can also return ProblemDetails response with a custom extension. ProblemDetails has an Extensions property, which is a dictionary for adding custom information. Here we&#8217;re creating a collection of key-value pairs.</p>



<pre class="wp-block-code"><code>app.MapGet("/customerror", () =&gt;
{
    var ext = new List&lt;KeyValuePair&lt;string, object?&gt;&gt; { new("test", "value") };
    return TypedResults.Problem("This is an error with extensions", extensions: ext);
});</code></pre>



<p>This endpoint defines a GET route at /customerror that returns an RFC 7807 error response.</p>
<p>How it works:</p>
<ul>
<li>app.MapGet(&#8220;/customerror&#8221;, &#8230;) registers a GET endpoint that responds to requests made to /problem.</li>
<li>A collection of extension properties is created containing a single key-value pair:</li>
<li>Key: &#8220;test&#8221;</li>
<li>Value: &#8220;value&#8221;</li>
<li>TypedResults.Problem() creates a Problem Details response, which is the standard format for communicating HTTP API errors. The first argument (&#8220;This is an error with extensions&#8221;) populates the detail property of the response.</li>
<li>The extensions parameter adds custom fields to the Problem Details payload. These properties appear alongside the standard fields (type, title, status, detail, and instance) and can be used to include additional error information.</li>
</ul>
<p>A response from this endpoint resembles the following:</p>



<pre class="wp-block-code"><code>{ 
    "type": "about:blank", 
    "title": "An error occurred.", 
    "status": 500, 
    "detail": "This is an error with extensions", 
    "test": "value" 
}</code></pre>



<p>Using the extensions property is useful when clients need additional context, such as correlation IDs, error codes, validation metadata, or other application-specific information, while still conforming to the Problem Details specification.</p>
<h2 id="cproblemdetails">Customize validation error responses using IProblemDetailsService</h2>
<p>IProblemDetailsService is the service responsible for creating and writing ProblemDetails responses in ASP.NET Core. When we call:</p>



<pre class="wp-block-code"><code>builder.Services.AddProblemDetails();</code></pre>



<p>ASP.NET Core registers an implementation of IProblemDetailsService in the dependency injection (DI) container.</p>
<p>Why does it exist? Instead of every middleware or endpoint manually creating a ProblemDetails object, they can delegate the work to IProblemDetailsService. This keeps error handling centralized and consistent.</p>
<p>Think of it like this:</p>



<pre class="wp-block-code"><code>Exception occurs
        │
        ▼
UseExceptionHandler middleware
        │
        ▼
IProblemDetailsService
        │
        ▼
Creates ProblemDetails
        │
        ▼
Writes JSON response</code></pre>



<div class="note">Example</div>
<p>Suppose you have custom middleware. Instead of doing this:</p>



<pre class="wp-block-code"><code>app.Use(async (context, next) =&gt;
{
    try
    {
        await next();
    }
    catch
    {
        context.Response.StatusCode = 500;

        await context.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Title = "Unexpected Error",
            Status = 500
        });
    }
});</code></pre>



<p>You can use IProblemDetailsService.</p>



<pre class="wp-block-code"><code>app.Use(async (context, next) =&gt;
{
    var problemService =
        context.RequestServices.GetRequiredService&lt;IProblemDetailsService&gt;();

    try
    {
        await next();
    }
    catch
    {
        await problemService.TryWriteAsync(new ProblemDetailsContext
        {
            HttpContext = context,
            ProblemDetails = new ProblemDetails
            {
                Title = "Unexpected Error",
                Status = 500
            }
        });
    }
});</code></pre>



<p>Now the response is generated using the same service used throughout the application.</p>
<p><b>ProblemDetailsContext</b> : TryWriteAsync() accepts a ProblemDetailsContext.</p>



<pre class="wp-block-code"><code>new ProblemDetailsContext
{
    HttpContext = context,
    ProblemDetails = new ProblemDetails
    {
        Title = "Invalid Request",
        Detail = "The supplied data is incorrect.",
        Status = 400
    }
}</code></pre>



<p>The context contains:</p>
<ul>
<li>HttpContext</li>
<li>ProblemDetails</li>
<li>Exception (optional)</li>
<li>Additional metadata</li>
</ul>
<h2 id="file">Returning File response from Minimal API</h2>
<p>In Minimal APIs, the most commonly used approach for returning a file is <span class="term">TypedResults.File</span>. It accepts either a <span class="term">byte[]</span> or a <span class="term">Stream</span> and returns a <span class="term">FileContentHttpResult</span> or <span class="term">FileStreamHttpResult</span>, respectively.</p>
<p>The endpoint defines a Minimal API route that generates a PDF in memory and returns it as a downloadable file.</p>



<pre class="wp-block-code"><code>app.MapGet("/pdfdownload", () =&gt;
{
    // TypedResults.File with a byte&#91;] returns a FileContentHttpResult
    byte&#91;] pdf = GenerateReport();
    return TypedResults.File(pdf, "application/pdf", "work.pdf");
});</code></pre>



<p>Here&#8217;s what each part does:</p>
<ul>
<li><span class="code">app.MapGet(&#8220;/pdfdownload&#8221;, &#8230;)</span> registers a GET endpoint at <u>/pdfdownload</u>. When a client sends a GET request to this URL, the lambda expression is executed.</li>
<li><span class="code">byte[] pdf = GenerateReport()</span> calls a method that generates the PDF content and returns it as a <u>byte[]</u>. The entire PDF is stored in memory before it is sent to the client.</li>
<li><span class="code">TypedResults.File(&#8230;)</span> creates a file response from the byte array. Since the input is a <u>byte[]</u>, it returns a <u>FileContentHttpResult</u>.</li>
</ul>
<p>The TypedResults.File method takes three arguments:</p>
<ol>
<li>pdf – The file content as a byte[].</li>
<li>&#8220;application/pdf&#8221; – The MIME type (Content-Type) of the response, which tells the client that the file is a PDF document.</li>
<li>&#8220;work.pdf&#8221; – The suggested filename for the downloaded file. ASP.NET Core includes this value in the Content-Disposition response header so that browsers typically download the file using this name.</li>
</ol>
<p>When a client requests <u>/pdfdownload</u>, the server generates the PDF, sets the appropriate HTTP headers, and sends the file to the client. Most web browsers will prompt the user to download the file (or open it with a PDF viewer), using work.pdf as the default filename.</p>
<p>This endpoint creates a stream in memory and returns it as a file to the client.</p>



<pre class="wp-block-code"><code>app.MapGet("/filedownload", () =&gt;
{
    // TypedResults.File with a Stream returns a FileStreamHttpResult
    Stream stream = new MemoryStream("Hello, World!"u8.ToArray());
    return TypedResults.File(stream, "application/octet-stream");
});</code></pre>



<p>Here&#8217;s how it works:</p>
<ul>
<li><span class="code">app.MapGet(&#8220;/download&#8221;, &#8230;)</span> registers a GET endpoint at <u>/download</u>. The lambda expression runs whenever a client sends a GET request to this URL.</li>
<li><span class="code">Stream stream = new MemoryStream(&#8220;Hello, World!&#8221;u8.ToArray())</span> creates a <u>MemoryStream</u> containing the text &#8220;Hello, World!&#8221;.</li>
<li>&#8220;Hello, World!&#8221; is a UTF-8 string literal that produces a <span class="code">ReadOnlySpan<byte></span> containing the UTF-8 encoded bytes of the string. <span class="code">.ToArray()</span> converts the span into a byte[].</li>
<li><span class="code">new MemoryStream(&#8230;)</span> wraps the byte array in a stream so it can be read and sent to the client.</li>
<li><span class="code">TypedResults.File(stream, &#8220;application/octet-stream&#8221;)</span> creates a file response from the stream. Because the input is a Stream, it returns a <u>FileStreamHttpResult</u>.</li>
</ul>
<p>The <span class="code">TypedResults.File</span> method takes two arguments:</p>
<ol>
<li>stream – The stream containing the file data.</li>
<li>&#8220;application/octet-stream&#8221; – The MIME type of the response. This generic binary content type tells the client that the response contains arbitrary binary data.</li>
</ol>
<p>Unlike the previous example, this overload does not specify a download filename. As a result, ASP.NET Core doesn&#8217;t include a Content-Disposition header with a filename, so the browser determines how to handle the response. Some browsers may display the content, while others may download it with a generated filename.</p>
<div class="note">What is Content-Disposition header?</div>
<p>The <span class="term">Content-Disposition</span> header is an HTTP response header that tells the client (such as a web browser) how the returned content should be handled. It is commonly used to indicate whether a file should be displayed inline or downloaded, and to provide a default filename.</p>
<p>Syntax:</p>



<pre class="wp-block-code"><code>Content-Disposition: disposition-type; filename="filename.ext"</code></pre>



<p>The two most common disposition types are:</p>
<ol>
<li>inline – Display the content in the browser if possible.</li>
<li>attachment – Prompt the user to download the file.</li>
</ol>
<p>Example 1: Download a file</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="work.pdf"</code></pre>



<p>Example 2: Display a file in the browser</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="work.pdf"</code></pre>



<p>When we write:</p>



<pre class="wp-block-code"><code>return TypedResults.File(pdf, "application/pdf", "work.pdf");</code></pre>



<p>ASP.NET Core automatically sets a Content-Disposition header similar to:</p>



<pre class="wp-block-code"><code>Content-Disposition: attachment; filename="work.pdf"</code></pre>



<p>This tells the browser to download the file and use work.pdf as the default filename.</p>
<p>If you omit the filename:</p>



<pre class="wp-block-code"><code>return TypedResults.File(stream, "application/octet-stream");</code></pre>



<p>ASP.NET Core doesn&#8217;t set a Content-Disposition header with a filename. The browser then decides how to handle the response based on the content type and its own behavior.</p>
<h3 id="openapi">OpenAPI support for File Response</h3>
<p>File result types do not automatically add response metadata to the generated OpenAPI document. To ensure the response is accurately described in the OpenAPI specification, you must explicitly provide the required response metadata.</p>
<p>For this we use <span class="term">Produces<TResponse>()</span> method to provide the OpenAPI metadata for the response. This metadata defines the response&#8217;s status code, content type, and schema in the generated OpenAPI document. For example we specify content type as &#8220;application/pdf&#8221; for a pdf file.</p> 
<p>We now have added the <span class="term">Produces<TResponse>()</span> method to provide the OpenAPI metadata for the pdf file.</p>



<pre class="wp-block-code"><code>app.MapGet("/pdfdownload", () =&gt;
{
    // TypedResults.File with a byte&#91;] returns a FileContentHttpResult
    byte&#91;] pdf = GenerateReport();
    return TypedResults.File(pdf, "application/pdf", "work.pdf");
})
.Produces(StatusCodes.Status200OK, contentType: "application/pdf");</code></pre>



<p>The .Produces method adds response metadata to the generated OpenAPI document. In this example:</p>
<ul>
<li>StatusCodes.Status200OK specifies that the endpoint returns an HTTP 200 OK response.</li>
<li>contentType: &#8220;application/pdf&#8221; indicates that the response body is a PDF document.</li>
</ul>
<p>This metadata allows OpenAPI tools such as Swagger UI to correctly document the endpoint as returning a PDF file.</p>
<p>The generated response section in the OpenAPI document is similar to:</p>



<pre class="wp-block-code"><code>responses:
  "200":
    description: OK
    content:
      application/pdf: {}</code></pre>



<p>Without the .Produces call, the endpoint still returns the PDF correctly at runtime, but the generated OpenAPI document lacks the response metadata needed to accurately describe the file response.</p>
<p>In the same way we have used Produces method for the above second example.</p>



<pre class="wp-block-code"><code>app.MapGet("/filedownload", () =&gt;
{
    // TypedResults.File with a Stream returns a FileStreamHttpResult
    Stream stream = new MemoryStream("Hello, World!"u8.ToArray());
    return TypedResults.File(stream, "application/octet-stream");
})
.Produces&lt;Stream&gt;(contentType: MediaTypeNames.Application.Octet);</code></pre>



<p>For text content, such as CSV or plain text, use contentType: &#8220;text/plain&#8221;:</p>



<pre class="wp-block-code"><code>app.MapGet("/download/message", () =&gt;
{
    string content = "Hello, World!";

    byte&#91;] bytes = Encoding.UTF8.GetBytes(content);

    // TypedResults.File with byte&#91;] returns FileContentHttpResult
    return TypedResults.File(
        bytes,
        "text/plain",
        "message.txt");
})
.Produces(StatusCodes.Status200OK, contentType: "text/plain");</code></pre>



<p>For CSV or plain text where the response body is a text value, use string as the TResponse for this case and contentType: &#8220;text/csv&#8221;:</p>



<pre class="wp-block-code"><code>app.MapGet("/users/csv", () =&gt;
{
    string csv = """
                 Id,Name,Email
                 1,John,john@example.com
                 2,Jane,jane@example.com
                 """;

    return csv;
})
.Produces&lt;string&gt;(StatusCodes.Status200OK, contentType: "text/csv");</code></pre>



<h2 id="cache">File Responses for Conditional Requests and Cache Validation</h2>
<p>File responses can support HTTP conditional requests to improve caching efficiency. By including cache validation headers such as &#8220;ETag&#8221; and &#8220;Last-Modified&#8221;, the server allows clients to determine whether a cached copy of a file is still valid.</p>
<p>When a client makes a request, it can include conditional headers such as <span class="term">If-None-Match</span> (using an ETag) or <span class="term">If-Modified-Since</span> (using a Last-Modified timestamp). The server can use these values to check whether the file has changed.</p>
<p>If the file has not changed, the server returns 304 Not Modified, allowing the client to use its cached copy without downloading the file again.
If the file has changed, the server returns 200 OK with the updated file content and updated cache metadata.</p>
<p>In ASP.NET Core Minimal APIs, file results can include ETag and Last-Modified values to provide cache validation information. However, the application must implement the conditional request logic to compare incoming request headers and decide whether to return 304 Not Modified.</p>
<p>Check the below example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapGet(&quot;/shoesale&quot;, (
    &#x5B;FromHeader(Name = &quot;If-None-Match&quot;)] string? ifNoneMatch,
    &#x5B;FromHeader(Name = &quot;If-Modified-Since&quot;)] string? ifModifiedSince) =&gt;
{
    byte&#x5B;] data = File.ReadAllBytes(&quot;Products/SaleShoes.json&quot;);
    var lastModified = File.GetLastWriteTimeUtc(&quot;Products/SaleShoes.json&quot;);
    var etag = new EntityTagHeaderValue($&quot;\&quot;{Convert.ToHexString(SHA256.HashData(data))}\&quot;&quot;);

    return TypedResults.File(
        data,
        contentType: MediaTypeNames.Application.Json,
        lastModified: lastModified,
        entityTag: etag);
})
.Produces&lt;object&gt;(StatusCodes.Status200OK)
.Produces(StatusCodes.Status304NotModified);
</pre></div>


<div class="note">Explanation:</div>
<p>To enable cache validation, a file response from minimal api can include metadata such as:</p>
<ul>
<li>ETag: A unique identifier representing a specific version of the resource.</li>
<li>Last-Modified: The date and time when the resource was last changed.</li>
</ul>
<p>Clients can send conditional request headers based on this metadata:</p>
<ul>
<li>If-None-Match: Contains the previously received ETag value.</li>
<li>If-Modified-Since: Contains the previously received Last-Modified timestamp.</li>
</ul>
<p>The server compares the values provided by the client with the current resource metadata:</p>
<ul>
<li>If the resource has not changed, the server returns 304 Not Modified without sending the file content. The client uses its cached copy.</li>
<li>If the resource has changed, the server returns 200 OK with the updated file content and cache validation metadata.</li>
</ul>
<p>The parameters read request headers sent by the client.</p>



<pre class="wp-block-code"><code>&#91;FromHeader(Name = "If-None-Match")] string? ifNoneMatch,
&#91;FromHeader(Name = "If-Modified-Since")] string? ifModifiedSince)</code></pre>



<p>Reading the JSON file:</p>



<pre class="wp-block-code"><code>byte&#91;] data = File.ReadAllBytes("Products/SaleShoes.json");</code></pre>



<p>Getting the last modified time:</p>



<pre class="wp-block-code"><code>var lastModified = File.GetLastWriteTimeUtc("Products/SaleShoes.json");</code></pre>



<p>Creating an ETag:</p>



<pre class="wp-block-code"><code>var etag = new EntityTagHeaderValue(
    $"\"{Convert.ToHexString(SHA256.HashData(data))}\"");</code></pre>



<p>If SaleShoes.json changes, the hash changes, so the ETag changes.</p>
<p>Returning the file:</p>



<pre class="wp-block-code"><code>return TypedResults.File(
    data,
    contentType: MediaTypeNames.Application.Json,
    lastModified: lastModified,
    entityTag: etag);</code></pre>



<p>The response contains the json file along with Last-Modified and ETag values:</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Sat, 11 Jul 2026 10:30:00 GMT
ETag: "A1B2C3D4E5"</code></pre>



<p>The client can store these values for future requests.</p>
<p>OpenAPI metadata:</p>



<pre class="wp-block-code"><code>.Produces&lt;object&gt;(StatusCodes.Status200OK)
.Produces(StatusCodes.Status304NotModified);</code></pre>



<div class="note">If-None-Match</div>
<p>The client uses If-None-Match to ask the server:</p>



<pre class="wp-block-code"><code>"I already have the version of this resource identified by this ETag. Send it again only if it has changed."</code></pre>



<p>If the resource hasn&#8217;t changed, the server responds with 304 Not Modified instead of sending the resource again.</p>
<p><u>Step 1: Client requests a resource:</u></p>



<pre class="wp-block-code"><code>GET /shoesale HTTP/1.1</code></pre>



<p>The server responds with the JSON file and an ETag:</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/json
ETag: "A1B2C3D4"

&#91;
  {
    "name": "Running Shoe",
    "price": 50
  }
]</code></pre>



<p>The client stores: The response body and ETag value. Below is an ETag value:</p>



<pre class="wp-block-code"><code>"A1B2C3D4"</code></pre>



<p><u>Step 2: Client requests the resource again:</u></p>



<pre class="wp-block-code"><code>GET /shoesale HTTP/1.1
If-None-Match: "A1B2C3D4"</code></pre>



<p>The client is saying:</p>



<pre class="wp-block-code"><code>"I already have version A1B2C3D4. Send me the file only if the current version is different."</code></pre>



<p><u>Step 3: Server compares the ETag:</u></p>
<p>The server calculates the current ETag for the file.</p>
<p>Case 1: The file has not changed</p>
<p>Current ETag:</p>



<pre class="wp-block-code"><code>"A1B2C3D4"</code></pre>



<p>Client sent:</p>



<pre class="wp-block-code"><code>"A1B2C3D4"</code></pre>



<p>They match, so the server responds:</p>



<pre class="wp-block-code"><code>HTTP/1.1 304 Not Modified</code></pre>



<p>No response body is sent. The client continues using its cached copy.</p>
<p>Case 2: The file has changed:</p>
<p>Suppose the file was updated.</p>
<p>The server calculates a new ETag:</p>



<pre class="wp-block-code"><code>"F9E8D7C6"</code></pre>



<p>Now Client sends:</p>



<pre class="wp-block-code"><code>"A1B2C3D4"</code></pre>



<p>Server calculates:</p>



<pre class="wp-block-code"><code>"F9E8D7C6"</code></pre>



<p>The ETags do not match, so the server returns:</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/json
ETag: "F9E8D7C6"

&#91;
  {
    "name": "Running Shoe",
    "price": 45
  }
]</code></pre>



<p>The client updates its cache with the new file and ETag.</p>
<div class="note">If-Modified-Since</div>
<p>The client is essentially saying:</p>



<pre class="wp-block-code"><code>"Send me this resource only if it has been modified after this date."</code></pre>



<p>If the resource has not changed, the server responds with 304 Not Modified and does not send the resource body again.</p>
<p><u>1. First request</u></p>
<p>A client requests a file:</p>



<pre class="wp-block-code"><code>GET /shoesale HTTP/1.1</code></pre>



<p>The server returns the file and includes a Last-Modified header:</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Sat, 11 Jul 2026 10:30:00 GMT

&#91;
  {
    "name": "Running Shoe",
    "price": 50
  }
]</code></pre>



<p>The Last-Modified header tells the client:</p>



<pre class="wp-block-code"><code>"This file was last changed at this time."</code></pre>



<p>The client stores:</p>
<ol>
<li>The response body</li>
<li>The Last-Modified date</li>
</ol>
<p><u>2. Client makes a later request</u></p>
<p>When the client needs the file again, it sends:</p>



<pre class="wp-block-code"><code>GET /shoesale HTTP/1.1
If-Modified-Since: Sat, 11 Jul 2026 10:30:00 GMT</code></pre>



<p>The client is saying:</p>



<pre class="wp-block-code"><code>"I already have a copy from 10:30. Has it changed since then?"</code></pre>



<p><u>3. Server checks the modification date</u></p>



<pre class="wp-block-code"><code>var lastModified = File.GetLastWriteTimeUtc("Products/SaleShoes.json");</code></pre>



<p>Case 1: File has not changed</p>
<p>Client sends:</p>



<pre class="wp-block-code"><code>If-Modified-Since: 10:30</code></pre>



<p>Server file:</p>



<pre class="wp-block-code"><code>Last-Modified: 10:30</code></pre>



<p>The file is unchanged, so the server responds:</p>



<pre class="wp-block-code"><code>HTTP/1.1 304 Not Modified</code></pre>



<p>No file content is sent. The client uses its cached copy.</p>
<p>Case 2: File has changed</p>
<p>Client sends:</p>



<pre class="wp-block-code"><code>If-Modified-Since: 10:30</code></pre>



<p>Server file:</p>



<pre class="wp-block-code"><code>Last-Modified: 11:15</code></pre>



<p>The file is newer, so the server responds:</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Sat, 11 Jul 2026 11:15:00 GMT

&#91;
  {
    "name": "Running Shoe",
    "price": 45
  }
]</code></pre>



<p>The client receives the updated file.</p>
<h2 id="range">File Response for range requests</h2>
<p>In ASP.NET Core Minimal APIs, &#8220;File result support for range requests&#8221; refers to the ability of file-returning endpoints to handle the HTTP Range header automatically. This allows clients to download or stream only a portion of a file instead of the entire file.</p>
<p>This is particularly useful for:</p>
<ul>
<li>🎥 Video and audio streaming</li>
<li>📥 Resuming interrupted downloads</li>
<li>📄 Reading parts of large files</li>
<li>🚀 Reducing bandwidth usage</li>
</ul>
<h3>Without range request support</h3>
<p>Suppose you have a Minimal API endpoint:</p>



<pre class="wp-block-code"><code>app.MapGet("/download", () =&gt;
{
    return Results.File(
        "Files/report.pdf",
        "application/pdf");
});</code></pre>



<p>If a client requests:</p>



<pre class="wp-block-code"><code>GET /download HTTP/1.1
Range: bytes=0-999</code></pre>



<p>the server ignores the Range header and returns the entire file is sent.</p>



<pre class="wp-block-code"><code>HTTP/1.1 200 OK
Content-Length: 5000000</code></pre>



<p>Here the entire 5 MB file is sent.</p>
<h3>With range request support</h3>
<p>ASP.NET Core lets you enable range processing by setting <span class="term">enableRangeProcessing</span> to <span class="term">true</span>.</p>



<pre class="wp-block-code"><code>app.MapGet("/download", () =&gt;
{
    return Results.File(
        path: "Files/report.pdf",
        contentType: "application/pdf",
        enableRangeProcessing: true);
});</code></pre>



<p>Now, if the client requests:</p>



<pre class="wp-block-code"><code>GET /download HTTP/1.1
Range: bytes=0-999</code></pre>



<p>ASP.NET Core automatically:</p>
<ul>
<li>Parses the Range header.</li>
<li>Validates the requested byte range.</li>
<li>Reads only that portion of the file.</li>
<li>Returns the correct HTTP response.</li>
</ul>
<p>The response becomes:</p>



<pre class="wp-block-code"><code>HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 0-999/5000000
Content-Length: 1000</code></pre>



<p>Only the first 1000 bytes are sent.</p>
<div class="note">Example: Video streaming</div>
<p>This endpoint exposes a video file through an ASP.NET Core Minimal API and enables HTTP Range Requests, allowing clients (such as browsers or video players) to request only portions of the video.</p>



<pre class="wp-block-code"><code>app.MapGet("/catvideo/{id}", (string id, &#91;FromHeader(Name = "Range")] string? range) =&gt;
{
    var bytes = GetVideo(id);

    return TypedResults.File(
        bytes,
        contentType: "video/mp4",
        fileDownloadName: "cat.mp4",
        enableRangeProcessing: true);
})
.Produces&lt;Stream&gt;(StatusCodes.Status200OK, "video/mp4")
.Produces&lt;Stream&gt;(StatusCodes.Status206PartialContent, "video/mp4")
.Produces(StatusCodes.Status416RangeNotSatisfiable);</code></pre>



<p>Let us understand the code.</p>
<p>1. Mapping the endpoint</p>



<pre class="wp-block-code"><code>app.MapGet("/catvideo/{id}", ...)</code></pre>



<p>Creates a GET endpoint.</p>
<p>Example request:</p>



<pre class="wp-block-code"><code>GET /catvideo/123</code></pre>



<p>Here:</p>



<pre class="wp-block-code"><code>id = "123"</code></pre>



<p>2. Reading the Range header</p>



<pre class="wp-block-code"><code>&#91;FromHeader(Name = "Range")] string? range</code></pre>



<p>This tells ASP.NET Core:</p>



<pre class="wp-block-code"><code>Read the HTTP Range header and bind it to the range parameter.</code></pre>



<p>For example, if the client sends:</p>



<pre class="wp-block-code"><code>GET /catvideo/123 HTTP/1.1
Range: bytes=1000-5000</code></pre>



<pre class="wp-block-code"><code>&lt;p&gt;then&lt;/p&gt;</code></pre>



<pre class="wp-block-code"><code>range == "bytes=1000-5000"</code></pre>



<p>If the client doesn&#8217;t send a Range header:</p>



<pre class="wp-block-code"><code>GET /catvideo/123</code></pre>



<p>then</p>



<pre class="wp-block-code"><code>range == null</code></pre>



<p>Why isn&#8217;t range used? Notice that the code never references the range variable. That&#8217;s because:</p>



<pre class="wp-block-code"><code>TypedResults.File(..., enableRangeProcessing: true)</code></pre>



<p>already reads and processes the Range header internally from the HTTP request. The range parameter is therefore optional and is often included only for: logging, debugging, custom validation, or documenting that the endpoint supports range requests.</p>
<p>You could remove it entirely, and range processing would still work.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapGet(&quot;/catvideo/{id}&quot;,
    (string id,
     &#x5B;FromHeader(Name = &quot;Range&quot;)] string? range,
     ILogger&lt;Program&gt; logger) =&gt;
{
    logger.LogInformation(
        &quot;Video requested. Id: {VideoId}, Range: {Range}&quot;,
        id,
        range ?? &quot;&lt;entire file&gt;&quot;);

    var bytes = GetVideo(id);

    logger.LogInformation(
        &quot;Serving video &#039;{VideoId}&#039; ({Size} bytes) with range processing {Enabled}.&quot;,
        id,
        bytes.Length,
        true);

    return TypedResults.File(
        bytes,
        contentType: &quot;video/mp4&quot;,
        fileDownloadName: &quot;cat.mp4&quot;,
        enableRangeProcessing: true);
})
.Produces&lt;Stream&gt;(StatusCodes.Status200OK, &quot;video/mp4&quot;)
.Produces&lt;Stream&gt;(StatusCodes.Status206PartialContent, &quot;video/mp4&quot;)
.Produces(StatusCodes.Status416RangeNotSatisfiable);
</pre></div>


<p>3. Loading the video</p>



<pre class="wp-block-code"><code>var bytes = GetVideo(id);</code></pre>



<p>Suppose</p>



<pre class="wp-block-code"><code>byte&#91;] bytes = File.ReadAllBytes("cat.mp4");</code></pre>



<p>Now bytes contains the entire video in memory. For very large videos, using a Stream is generally preferable to avoid loading the whole file into memory.</p>
<p>4. Returning the file</p>



<pre class="wp-block-code"><code>return TypedResults.File(
    bytes,
    contentType: "video/mp4",
    fileDownloadName: "cat.mp4",
    enableRangeProcessing: true);</code></pre>



<p>Each parameter has a purpose:</p>



<pre class="wp-block-code"><code>bytes</code></pre>



<p>The content to send to the client.</p>
<p>contentType</p>



<pre class="wp-block-code"><code>"video/mp4"</code></pre>



<p>Sets the HTTP response header:</p>



<pre class="wp-block-code"><code>Content-Type: video/mp4</code></pre>



<p>So browsers know it&#8217;s an MP4 video.</p>
<p>fileDownloadName</p>



<pre class="wp-block-code"><code>"cat.mp4"</code></pre>



<p>This influences the Content-Disposition header. If the browser downloads the file, it suggests the filename cat.mp4.</p>
<p>enableRangeProcessing</p>



<pre class="wp-block-code"><code>true</code></pre>



<p>This is the key setting. It tells ASP.NET Core:</p>
<ul>
<li>Look for the Range header.</li>
<li>Validate the requested byte range.</li>
<li>Send only the requested bytes if the range is valid.</li>
<li>Return 206 Partial Content.</li>
<li>Return 416 Range Not Satisfiable for invalid ranges.</li>
<li>Include Accept-Ranges: bytes in the response.</li>
</ul>
<p>Without this flag, the framework would ignore the Range header and return the entire file with 200 OK.</p>
<p>5. Example request without a Range header</p>
<p>Client:</p>



<pre class="wp-block-code"><code>GET /catvideo/123</code></pre>



<p>Response:</p>



<pre class="wp-block-code"><code>200 OK
Content-Type: video/mp4
Content-Length: 52428800</code></pre>



<p>The full 50 MB video is returned.</p>
<p>6. Example request with a Range header</p>
<p>Client:</p>



<pre class="wp-block-code"><code>GET /catvideo/123
Range: bytes=0-999999</code></pre>



<p>The framework sends only the first 1,000,000 bytes.</p>
<p>Response:</p>



<pre class="wp-block-code"><code>206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 0-999999/52428800
Content-Length: 1000000</code></pre>



<p>This is how browsers can start playing a video before downloading the entire file.</p>
<p>7. Invalid Range</p>
<p>Suppose the file size is:</p>



<pre class="wp-block-code"><code>50 MB</code></pre>



<p>but the client requests:</p>



<pre class="wp-block-code"><code>Range: bytes=100000000-200000000</code></pre>



<p>Those byte positions don&#8217;t exist.</p>
<p>ASP.NET Core responds:</p>



<pre class="wp-block-code"><code>416 Range Not Satisfiable</code></pre>



<p>along with a Content-Range header indicating the valid total size.</p>
<p>8. The .Produces() calls</p>
<p>These methods don&#8217;t change the runtime behavior of the endpoint. Instead, they describe the possible responses for API metadata and OpenAPI/Swagger generation.</p>
<p>Successful full response</p>



<pre class="wp-block-code"><code>.Produces&lt;Stream&gt;(
    StatusCodes.Status200OK,
    "video/mp4")</code></pre>



<p>Documents that the endpoint can return:</p>



<pre class="wp-block-code"><code>200 OK
Content-Type: video/mp4</code></pre>



<p>when the entire file is sent.</p>
<p>In case of Partial response:</p>



<pre class="wp-block-code"><code>.Produces&lt;Stream&gt;(
    StatusCodes.Status206PartialContent,
    "video/mp4")</code></pre>



<p>Documents that the endpoint may return:</p>



<pre class="wp-block-code"><code>206 Partial Content</code></pre>



<p>when it fulfills a valid range request.</p>
<p>In case of Invalid range:</p>



<pre class="wp-block-code"><code>.Produces(
    StatusCodes.Status416RangeNotSatisfiable);</code></pre>



<p>Documents that the endpoint may return:</p>



<pre class="wp-block-code"><code>416 Range Not Satisfiable</code></pre>



<p>for an invalid Range header.</p>
<p>How it all works together</p>
<ul>
<li>A client requests /catvideo/123.</li>
<li>If there is no Range header, ASP.NET Core returns the full video with 200 OK.</li>
<li>If there is a valid Range header, ASP.NET Core returns only the requested bytes with 206 Partial Content.</li>
<li>If the range is invalid, it returns 416 Range Not Satisfiable.</li>
<li>The .Produces() methods advertise these possible responses in the API&#8217;s metadata.</li>
</ul>
<p>This built-in support is what enables smooth seeking in video players and resumable downloads without requiring you to implement byte-range parsing or response handling yourself.</p>
<div class="note">Conclusion</div>
<p>In this tutorial we covered everything related to <b>Responses in Minimal API</b>. Mastering it is an important step toward building high-quality ASP.NET Core applications. Whether you are developing a small project, a RESTful web service, or a large-scale enterprise application, applying the concepts and best practices discussed in this guide will help you create APIs that are performant, scalable, maintainable, and easy for clients to consume. This knowledge forms a strong foundation for developing robust, production-ready web applications using ASP.NET Core Minimal APIs.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-response/">Complete Guide to Minimal API Response</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-minimal-api-response/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core Minimal API Parameter Binding &#8211; full guide with codes</title>
		<link>https://www.yogihosting.com/aspnet-core-minimal-api-parameter-binding/</link>
					<comments>https://www.yogihosting.com/aspnet-core-minimal-api-parameter-binding/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Sat, 04 Jul 2026 17:38:58 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22974</guid>

					<description><![CDATA[<p>In our last tutorial we created an ASP.NET Core Minimal API from Start till Finish. We move further to understand how Parameter Bindings works in Minimal APIS. Before we dive into parameter binding we have to understand what are routes and handlers. Page Contents Routes, Handlers and Parameters Route Constraints Parameter Binding in Minimal API [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-parameter-binding/">ASP.NET Core Minimal API Parameter Binding &#8211; full guide with codes</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In our last tutorial we created an <a href="https://www.yogihosting.com/aspnet-core-minimal-api/">ASP.NET Core Minimal API from Start till Finish</a>. We move further to understand how  Parameter Bindings works in Minimal APIS.</p>
<p>Before we dive into parameter binding we have to understand what are routes and handlers.</p>



<span id="more-22974"></span>



<div class="marginTop10" id="contentTable">
<div class="title"><p class="left">Page Contents</p><p class="right"><span title="click to toggle"></span></p></div>
<nav>
<ul>
<li><a href="#routes">Routes, Handlers and Parameters</a>
<ul>
<li><a href="#constraints">Route Constraints</a></li>
</ul>
</li>
<li><a href="#binding">Parameter Binding in Minimal API</a>
<ul>
<li><a href="#automatic">Example of Automatic Parameter Binding</a></li>
<li><a href="#explicit">Example of Explicit Parameter Binding</a></li>
<li><a href="#asparameters">Parameter Binding with [AsParameters] attribute</a></li>
<li><a href="#optional">Optional Parameters</a></li>
<li><a href="#di">Parameter binding with Dependency Injection</a></li>
<li><a href="#special">Binding .NET Special Types</a></li>
</ul>
</li>
<li><a href="#fileupload">File uploads using IFormFile and IFormFileCollection</a>
<ul>
<li><a href="#antiforgery">IAntiforgery to prevent Cross-Site Request Forgery (XSRF/CSRF) attacks</a></li>
</ul>
</li>
<li><a href="#collections">Bind to collections and complex types from forms</a></li>
<li><a href="#bindasync">Custom Binding with BindAsync method</a></li>
</ul>
</nav>
</div>
<h2 id="routes">Routes, Handlers and Parameters</h2>
<p>In ASP.NET Core Minimal APIs, a route defines how an incoming HTTP request—identified by its URL path and HTTP method is mapped to a specific handler or delegate that contains the application&#8217;s business logic. Minimal APIs register endpoints directly on the WebApplication instance resulting in a lightweight, streamlined approach with minimal configuration and overhead.</p>
<p>Route handlers are methods that execute when the route matches. Route handlers can be a lambda expression, a local function, an instance method, or a static method.</p>



<pre class="wp-block-code"><code>// Example of lambda expression
app.MapGet("/example1", () =&gt; "This is lambda expression");


// Example of local function

string LocalFunction() =&gt; "This is local function";

app.MapGet("/example2", LocalFunction);


// Example of Instance method

app.MapGet("/example3", handler.Hello);

class HelloHandler
{
    public string Hello()
    {
        return "Hello Instance method";
    }
}


// Example of static method

app.MapGet("/", HelloHandler.Hello);

class HelloHandler
{
    public static string Hello()
    {
        return "Hello static method";
    }
}</code></pre>



<p>A Route parameter is a variable segment of a URL that allows values to be passed from the request URL to an endpoint, controller action, or Minimal API handler.</p>
<p>In the below example the route &#8211; &#8220;/products/books&#8221; has a single parameter called name. This route will return the message &#8220;The product is books&#8221;. In the same way when the route &#8220;/products/shoes&#8221; is called then it returns the message &#8220;The product is shoes&#8221;.</p>



<pre class="wp-block-code"><code>app.MapGet("/products/{name}", (string name) =&gt; $"The product is {name}");</code></pre>



<p>In the below examples we have 2 route parameters countryName and cityName.</p>



<pre class="wp-block-code"><code>app.MapGet("/country/{countryName}/city/{cityName}", (string countryName, string cityName) =&gt; $"The country is {countryName} and city is {cityName}");</code></pre>



<p>The route <span class="term">/country/India/city/Lucknow</span> will return &#8220;The country is India and City is Lucknow&#8221;.</p>
<h3 id="constraints">Route Constraints</h3>
<p>Route constraints are used to restrict which URLs match a Minimal API route by validating route parameter values. They help ensure that only requests with the correct format reach a specific action or endpoint.</p>
<p>In the below code we have added an int constraint to the &#8220;id&#8221; parameter so that only int values reaches it.</p>



<pre class="wp-block-code"><code>app.MapGet("/example/{id:int}", (int id) =&gt;  $"The value of id is {id}");</code></pre>



<p>The matching format for the above route &#8211; /example/1, /example/10, /example/99. The route /example/hello will not match since id value in the route is string (i.e. hello).</p>     
<p>In the below case we have not applied any constraint.</p>



<pre class="wp-block-code"><code>app.MapGet("/example/{id}", (string id) =&gt;  $"The value of id is {id}");</code></pre>



<p>The matching format for the above route &#8211; /example/1, /example/hello, /example/hello99.</p>     
<p>We can also use regex route constraint to match a route parameter against a regular expression. For example in the below code we restrict the following for the slug parameter.</p>
<ol>
<li>Alphabets from a to z including capital letters.</li>
<li>Numbers from 0 to 9.</li>
<li>Characters _ and -.</li>
</ol>



<pre class="wp-block-code"><code>app.MapGet("/posts/{slug:regex(^&#91;a-z0-9_-]+$)}", (string slug) =&gt; $"Post {slug}");</code></pre>



<p>The matching format in this case are:</p>
<ul>
<li>posts/aa33</li>
<li>/posts/AA33</li>
<li>/posts/aa-33</li>
<li>/posts/aa_33</li>
</ul>
<p>The non-matching formats include.</p>
<ul>
<li>/posts/aa.33</li>
<li>/posts/aa/33</li>
</ul>
<div class="note">Catch All routes Wildcard</div>
<p>The <span class="term">*</span> character is used as a catch-all parameter. It matches the remainder of the URL path, including multiple path segments. The below example uses * to match all the remainder for the URL path after &#8220;match&#8221;.</p>



<pre class="wp-block-code"><code>app.MapGet("/match/{*slug}", (string slug) =&gt; $"Routing to {slug}");</code></pre>



<p>Examples of formats matched in this case are.</p>
<ul>
<li>/match/aa</li>
<li>/match/aa/aa/dd</li>
</ul>
<h2 id="binding">Parameter Binding in Minimal API</h2>
<p>Parameter binding is the process of mapping incoming request data to the strongly typed parameters defined by route handlers. A binding source specifies where the parameter values are obtained from. Binding sources can be explicitly defined or automatically inferred based on the HTTP method and the parameter type.</p>
<p>Supported binding sources:</p>
<ul>
<li>Route values</li>
<li>Query string</li>
<li>Header</li>
<li>Body (as JSON)</li>
<li>Form values</li>
<li>Services through dependency injection</li>
<li>Custom</li>
</ul>
<h3>Default parameter binding rules</h3>
<p>In ASP.NET Core Minimal APIs, parameter binding follows a set of default rules to determine where each parameter value should come from. In most cases, you don&#8217;t need to specify attributes like <span class="term">[FromQuery]</span> or <span class="term">[FromRoute]</span>.</p>
<p>Parameters are bound in the following order:</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Parameter type</th>
<th>Default binding source</th>
</tr>
</thead>
<tbody>
<tr>
<td>Route parameter (name matches route template)</td>
<td>Route values</td>
</tr>
<tr>
<td>Simple types (int, string, bool, Guid, DateTime, etc.) not in route</td>
<td>Query String</td>
</tr>
<tr>
<td>Complex types</td>
<td>Request body in JSON</td>
</tr>
<tr>
<td>IFormFile, IFormFileCollection</td>
<td>Form data</td>
</tr>
<tr>
<td>Types registered in DI</td>
<td>Dependency Injection</td>
</tr>
</tbody> 
</table>
</div>
<div id="automatic" class="note">Example of Automatic Parameter Binding</div>
<p>See the below endpoint.</p>



<pre class="wp-block-code"><code>app.MapGet("/{id}", (int id,
                     int page,
                     &#91;FromHeader(Name = "X-MYCUSTOM-HEADER")] string customHeader,
                     Service service) =&gt; { });</code></pre>



<p>In the above example of a GET Endpoint, the Parameters and their respective Binding Sources are given below. Here automatic parameter bindings are done by .NET as given in the below table.</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Parameter</th>
<th>Binding Source</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>Route</td>
</tr>
<tr>
<td>page</td>
<td>Query String</td>
</tr>
<tr>
<td>customHeader</td>
<td>Header by the name &#8220;X-MYCUSTOM-HEADER&#8221;</td>
</tr>
<tr>
<td>service</td>
<td>Dependency Injection</td>
</tr>
</tbody> 
</table>
</div>
<p>By default, the GET, HEAD, OPTIONS, and DELETE HTTP methods do not bind parameters from the request body. To bind JSON data from the request body for these methods, explicitly use the [FromBody] attribute or read the body directly from the HttpRequest.</p>
<p>The HTTP POST method uses a default binding source of body (as JSON). In the below example the Employee object will be bind from the body.</p>



<pre class="wp-block-code"><code>app.MapPost("/", (Employee emp) =&gt; { });</code></pre>



<div id="explicit" class="note">Example of Explicit Parameter Binding</div>
<p>See the below endpoint.</p>



<pre class="wp-block-code"><code>app.MapGet("/{id}", (&#91;FromRoute] int id,
                     &#91;FromQuery(Name = "p")] int page,
                     &#91;FromHeader(Name = "X-MYCUSTOM-HEADER")] string customHeader,
                     &#91;FromServices] Service service) =&gt; { });</code></pre>



<p>The above example does explicit parameter binding by the use of <span class="term">FromRoute</span>, <span class="term">FromQuery</span>, <span class="term">FromHeader</span>, and <span class="term">FromServices</span> attributes.</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Parameter</th>
<th>Binding Source</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>route value with the name id</td>
</tr>
<tr>
<td>page</td>
<td>query string with the name &#8220;p&#8221;</td>
</tr>
<tr>
<td>customHeader</td>
<td>Header by the name &#8220;X-MYCUSTOM-HEADER&#8221;</td>
</tr>
<tr>
<td>service</td>
<td>Dependency Injection</td>
</tr>
<tr>
<td>Special types like HttpContext, HttpRequest, HttpResponse, CancellationToken, ClaimsPrincipal</td>
<td>Automatically without explicit attributes</td>
</tr>
</tbody> 
</table>
</div>
<p>The <span class="code">[FromForm]</span> attribute binds form values explicitly as shown below. Note that if we don&#8217;t apply <span class="term">[FromForm]</span> attribute then by default binding source of body (as JSON) is applied by .NET.</p>



<pre class="wp-block-code"><code>app.MapPost("/", (&#91;FromForm] Employee emp) =&gt; { });</code></pre>



<div class="note">Read the Request with HttpRequest</div>
<p>With the HttpRequest object we can read request data directly from the HTTP request. In ASP.NET Core, HttpRequest is a class that represents the incoming HTTP request sent by the client to your application. It provides access to information such as the request method, URL, headers, query string, form data, cookies, and request body.</p>
<p>See the below example.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapGet(&quot;/{id}&quot;, (HttpRequest request) =&gt;
{
    var id = request.RouteValues&#x5B;&quot;id&quot;];
    var page = request.Query&#x5B;&quot;page&quot;];
    var customHeader = request.Headers&#x5B;&quot;X-MYCUSTOM-HEADER&quot;];

    // ...
});

app.MapPost(&quot;/&quot;, async (HttpRequest request) =&gt;
{
    var emp = await request.ReadFromJsonAsync&lt;Employee&gt;();

    // ...
});
</pre></div>


<h3 id="asparameters">Parameter Binding with [AsParameters] attribute</h3>
<p>The <span class="term">AsParameters</span> is an attribute that groups multiple parameters into a single object while still binding each property from its appropriate source. This helps keep route handler signatures clean and organized.</p>
<p>For example, there is an endpoint containing multiple parameters.</p>



<pre class="wp-block-code"><code>app.MapGet("/products/{id}",
    (int id, string? search, ILogger&lt;Program&gt; logger) =&gt;
{
    // ...
});</code></pre>



<p>We can change this endpoint by using AsParameters attribute which will be binding a custom type called &#8220;ProductRequest&#8221; as shown below.</p>



<pre class="wp-block-code"><code>app.MapGet("/products/{id}", (&#91;AsParameters] ProductRequest request) =&gt;
{
    // ...
});</code></pre>



<p>Finally we can define a custom class called <span class="term">ProductRequest.cs</span> containing all the parameters.</p>



<pre class="wp-block-code"><code>public class ProductRequest
{
    public int Id { get; set; }

    public string? Search { get; set; }

    public ILogger&lt;Program&gt; Logger { get; set; } = default!;
}</code></pre>



<p>Each property of the class is bound independently using the normal Minimal API binding rules:</p>
<ul>
<li>Route parameters → Route values</li>
<li>Simple types → Query string (by default)</li>
<li>Services → Dependency injection</li>
</ul>
<p>We can override the default binding source by using attributes such as <span class="term">[FromRoute], [FromQuery], [FromHeader], [FromServices], and [FromForm]</span>. See the below updated code of the class.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class SearchRequest
{
    &#x5B;FromRoute]
    public int Id { get; set; }

    &#x5B;FromQuery]
    public string? Search { get; set; }

    &#x5B;FromHeader(Name = &quot;X-Request-ID&quot;)]
    public string? RequestId { get; set; }

    &#x5B;FromServices]
    public ILogger&lt;Program&gt; Logger { get; set; } = default!;
}
</pre></div>


<h3 id="optional">Optional Parameters</h3>
<p>Parameters declared in route handlers are considered required. A route handler is invoked only when the incoming request includes all required parameters. If any required parameter is missing, the request fails with an error instead of executing the handler.</p>
<p>We have an endpoint:</p>



<pre class="wp-block-code"><code>app.MapGet("/products", (int pageNumber) =&gt; $"Requested page {pageNumber}");</code></pre>



<p>If we invoke the uri &#8211; <span class="term">/products</span> then we get the error saying &#8211;</p>



<pre class="wp-block-code"><code>BadHttpRequestException: Required parameter "int pageNumber" wasn't provided from query string.</code></pre>



<p>The solution to this is to make pageNumber optional, define the type as optional or provide a default value:</p>



<pre class="wp-block-code"><code>app.MapGet("/products", (int? pageNumber) =&gt; $"Requested page {pageNumber}");

app.MapGet("/products", (int pageNumber = 1) =&gt; $"Requested page {pageNumber}");</code></pre>



<h3 id="di">Parameter binding with Dependency Injection</h3>
<p>In Minimal APIs, parameters whose types are registered as services are automatically resolved through dependency injection. As a result, you don&#8217;t need to explicitly annotate them with the [FromServices] attribute. In the following example, both route handlers receive the service from the DI container and return the current time, even though only one uses [FromServices].</p>



<pre class="wp-block-code"><code>// Register the service 
builder.Services.AddSingleton&lt;TimeService&gt;();

// Minimal API Endpoint
app.MapGet("/time", (TimeService timeService) =&gt; { return $"Current time: {timeService.GetCurrentTime()}"; });

// &#91;FromServices] is optional
app.MapGet("/time", (&#91;FromServices] TimeService timeService) =&gt; { return $"Current time: {timeService.GetCurrentTime()}"; });

// TimeService class

public class TimeService { public string GetCurrentTime() =&gt; DateTime.Now.ToString("T"); }</code></pre>



<h3 id="special">Binding .NET Special Types</h3>
<p>Special Types are bound automatically by .NET without explicit attributes.</p>
<p>* <span class="term">HttpContext</span> : The context holds all the information of the current HTTP request or response.</p>



<pre class="wp-block-code"><code>app.MapGet("/", (HttpContext context) =&gt; context.Response.WriteAsync("Hello Minimal API"));</code></pre>



<p>* <span class="term">HttpRequest and HttpResponse</span> : HTTP request and HTTP response.</p>



<pre class="wp-block-code"><code>app.MapGet("/", (HttpRequest request, HttpResponse response) =&gt;
    response.WriteAsync($"Hello Minimal API {request.Query&#91;"name"]}"));</code></pre>



<p>* <span class="term">CancellationToken</span> : cancellation token associated with the current HTTP request.</p>



<pre class="wp-block-code"><code>app.MapGet("/", async (CancellationToken cancellationToken) =&gt; 
    await LongRunningRequestAsync(cancellationToken));</code></pre>



<p>* <span class="term">ClaimsPrincipal</span> : The user associated with the request, bound from HttpContext.User.</p>



<pre class="wp-block-code"><code>app.MapGet("/", (ClaimsPrincipal user) =&gt; user.Identity.Name);</code></pre>



<h2 id="fileupload">File uploads using IFormFile and IFormFileCollection</h2>
<p>To upload files in a Minimal API, the request must use the <span class="term">multipart/form-data</span> content type. Parameters of type <span class="term">IFormFile</span> and supported named file collections such as <span class="code">IReadOnlyList<T></span> are bound to files in the form data, and the parameter name in the route handler must match the corresponding form field name in the request. Use IFormFile and IReadOnlyList<T> when you need only the files whose form field name matches the parameter name.</p>
<p>When the parameter type is <span class="term">IFormFileCollection</span>, all uploaded files in the multipart/form-data request are bound to the collection, regardless of their form field names.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Here we are using IFormFile so the parameter name in the route handler (file) must match the corresponding form field name in the request

app.MapPost(&quot;/upload&quot;, async (IFormFile file) =&gt;
{
    // The Path.GetTempFileName() creates a zero-byte temporary file in the operating system&#039;s default temporary directory which is C:\Users\&lt;username&gt;\AppData\Local\Temp\
    var tempFile = Path.GetTempFileName();
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

// Here we are using IFormFileCollection so all uploaded files in the multipart/form-data request are bound to the collection, regardless of their form field names

app.MapPost(&quot;/upload_many&quot;, async (IFormFileCollection myFiles) =&gt;
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});
</pre></div>


<h3 id="antiforgery">IAntiforgery to prevent Cross-Site Request Forgery (XSRF/CSRF) attacks</h3>
<p>IAntiforgery is used to generate and validate anti-forgery (CSRF) tokens. It helps protect web applications from Cross-Site Request Forgery (CSRF) attacks, where a malicious site tricks a user&#8217;s browser into submitting unwanted requests.</p>
<p>To implement anti-forgery token generate a form with an anti-forgery token and an /upload endpoint. In the /upload endpoint validates the anti-forgery token in the incoming request. If validation fails, an <u>AntiforgeryValidationException</u> is thrown and the request is rejected.</p>
<p>The below minimal api code generates and validates anti-forgery (CSRF) tokens.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet(&quot;/&quot;, (HttpContext context, IAntiforgery antiforgery) =&gt;
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $&quot;&quot;&quot;
      &lt;html&gt;
        &lt;body&gt;
          &lt;form action=&quot;/upload&quot; method=&quot;POST&quot; enctype=&quot;multipart/form-data&quot;&gt;
            &lt;input name=&quot;{token.FormFieldName}&quot; type=&quot;hidden&quot; value=&quot;{token.RequestToken}&quot;/&gt;
            &lt;input type=&quot;file&quot; name=&quot;file&quot; placeholder=&quot;Upload an image...&quot; accept=&quot;.jpg, .jpeg, .png&quot; /&gt;
            &lt;input type=&quot;submit&quot; /&gt;
          &lt;/form&gt; 
        &lt;/body&gt;
      &lt;/html&gt;
    &quot;&quot;&quot;;

    return Results.Content(html, &quot;text/html&quot;);
});

app.MapPost(&quot;/upload&quot;, async Task&lt;Results&lt;Ok&lt;string&gt;, BadRequest&lt;string&gt;&gt;&gt; (IFormFile file, HttpContext context, IAntiforgery antiforgery) =&gt;
{
    await antiforgery.ValidateRequestAsync(context);
    var fileSaveName = Guid.NewGuid().ToString(&quot;N&quot;) + Path.GetExtension(file.FileName);
    await UploadFileWithName(file, fileSaveName);
    return TypedResults.Ok(&quot;File uploaded successfully!&quot;);
});

async Task UploadFileWithName(IFormFile file, string fileSaveName)
{
    var filePath = GetOrCreateFilePath(fileSaveName);
    await using var fileStream = new FileStream(filePath, FileMode.Create);
    await file.CopyToAsync(fileStream);
}

string GetOrCreateFilePath(string fileName, string filesDirectory = &quot;uploadFiles&quot;)
{
    var directoryPath = Path.Combine(app.Environment.ContentRootPath, filesDirectory);
    Directory.CreateDirectory(directoryPath);
    return Path.Combine(directoryPath, fileName);
}

app.Run();
</pre></div>


<p>If you run the above code a file upload form will be presented as shown by the below image:</p>



<p><img decoding="async" class="img-fluid" src="https://www.yogihosting.com/wp-content/uploads/2026/07/minimal-api-file-upload.png" alt="Minimal API File Upload" /></p>
<p>When you view the page source, you can see the form&#8217;s code which is given below.</p>



<pre class="wp-block-code"><code>&lt;form action="/upload" method="POST" enctype="multipart/form-data"&gt;
        &lt;input name="__RequestVerificationToken" type="hidden" value="CfDJ8CJAiS5rYE9AjJoXkn5DPsi3_4TdjyD6twIrKrDao6kZK04ZNuy20TaQTatwOOD4G2HHrYE4QcNUajMDm-ecYOLGtK2jaQf5opiWXPn6CpBuSzkv0V8UDNkkWKcHLw_TjsuA6X5NKlEPakzpXAJA85k"/&gt;
        &lt;input type="file" name="file" placeholder="Upload an image..." accept=".jpg, .jpeg, .png" /&gt;
        &lt;input type="submit" /&gt;
&lt;/form&gt;</code></pre>



<p>The anti-forgery token code is given inside the hidden input tag:</p>



<pre class="wp-block-code"><code>&lt;input name="__RequestVerificationToken" type="hidden" value="CfDJ8CJAiS5rYE9AjJoXkn5DPsi3_4TdjyD6twIrKrDao6kZK04ZNuy20TaQTatwOOD4G2HHrYE4QcNUajMDm-ecYOLGtK2jaQf5opiWXPn6CpBuSzkv0V8UDNkkWKcHLw_TjsuA6X5NKlEPakzpXAJA85k"/&gt;</code></pre>



<p>Try uploading a file. When anti-forgery token validation is successful then the file is uploaded successfully and we get the message &#8211; &#8220;File uploaded successfully!&#8221;. Check the below image.</p>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/07/antiforgery-validation-success.png" alt="Antiforgery Validation Success" title="Antiforgery Validation Success" class="img-fluid"/></p>
<p>Invalid anti-forgery token will give the error:</p>



<pre class="wp-block-code"><code>CryptographicException: The payload was invalid. For more information go to https://aka.ms/aspnet/dataprotectionwarning
AntiforgeryValidationException: The antiforgery token could not be decrypted.
BadHttpRequestException: Invalid anti-forgery token found when reading parameter "IFormFile file" from the request body as form.</code></pre>



<p>Check the below image where we have shown this error:</p>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/07/AntiforgeryValidationException.png" alt="Antiforgery Validation Exception" title="Antiforgery Validation Exception" class="img-fluid"/></p>
<div class="noteBlock">Just change the anti-forgery token from the developer tools of the browser before uploading a file. You will get the AntiforgeryValidationException.</div>
<div class="note">Explanation of the code:</div>
<p>First the <code>builder.Services.AddAntiforgery()</code> method registers the antiforgery service in your ASP.NET Core dependency injection container. Then we need to tell the app to validate the antiforgery tokens. This is done by the code &#8211; <code>app.UseAntiforgery()</code>.</p>
<p>The <span class="code">GetAndStoreTokens()</span> method is the primary IAntiforgery method for generating anti-forgery tokens. It creates the tokens needed for CSRF protection and stores the cookie token in the response. A full HTML form is generated in the / endpoint which contains the token. This form is returned in the API response.</p>
<p>Next on the /upload endpoint, the token is validated with the code &#8211; <code>await antiforgery.ValidateRequestAsync(context)</code>, and when the validation succeed then only the file is uploaded.</p>
<h2 id="collections">Bind to collections and complex types from forms</h2>
<p>Here we will see an example that binds a multi-part form input to a complex object. We will also use antiforgery services for validation of antiforgery tokens.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAntiforgery();

var app = builder.Build();

app.UseAntiforgery();

app.MapGet(&quot;/&quot;, (HttpContext context, IAntiforgery antiforgery) =&gt;
{
    var token = antiforgery.GetAndStoreTokens(context);
    var html = $&quot;&quot;&quot;
        &lt;html&gt;&lt;body&gt;
           &lt;form action=&quot;/job&quot; method=&quot;POST&quot; enctype=&quot;multipart/form-data&quot;&gt;
               &lt;input name=&quot;{token.FormFieldName}&quot; type=&quot;hidden&quot; value=&quot;{token.RequestToken}&quot; /&gt;
               &lt;input type=&quot;text&quot; name=&quot;name&quot; /&gt;
               &lt;input type=&quot;date&quot; name=&quot;dueDate&quot; /&gt;
               &lt;input type=&quot;submit&quot; /&gt; 
           &lt;/form&gt;
        &lt;/body&gt;&lt;/html&gt;
    &quot;&quot;&quot;;
    return Results.Content(html, &quot;text/html&quot;);
});

app.MapPost(&quot;/job&quot;, async Task&lt;Results&lt;Ok&lt;Work&gt;, BadRequest&lt;string&gt;&gt;&gt; 
               (&#x5B;FromForm] Work work, HttpContext context, IAntiforgery antiforgery) =&gt;
{
    try
    {
        await antiforgery.ValidateRequestAsync(context);
        return TypedResults.Ok(work);
    }
    catch (AntiforgeryValidationException e)
    {
        return TypedResults.BadRequest(&quot;Invalid antiforgery token&quot;);
    }
});

app.Run();

class Work
{
    public string Name { get; set; } = string.Empty;
    public DateTime DueDate { get; set; } = DateTime.Now.Add(TimeSpan.FromDays(1));
}
</pre></div>


<div class="note">Explanation:</div>
<p>The endpoint / will present a form with fields name and dueDate. We have to bind these fields to a Work.cs class.</p>
<p>The endpoint /job will bind the submitted form values to the Work class.</p>
<p>We have also used antiforgery services to support the generation and validation of antiforgery tokens.</p>
<p>In the below example binding to a complex type and a list of complex type is performed.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
app.MapPost(&quot;/items&quot;, (&#x5B;FromForm] List&lt;Item&gt; items) =&gt;
{
    return Results.Ok(items);
});

app.MapPost(&quot;/orders&quot;, (&#x5B;FromForm] CreateOrderRequest request) =&gt;
{
    return Results.Ok(request);
});

public class CreateOrderRequest
{
    public string Customer { get; set; } = &quot;&quot;;
    public List&lt;Item&gt; Items { get; set; } = &#x5B;];
}

public class Item
{
    public string Name { get; set; } = &quot;&quot;;
    public int Quantity { get; set; }
}
</pre></div>


<h2 id="bindasync">Custom Binding with BindAsync method</h2>
<p>BindAsync is useful when:</p>
<ul>
<li>You want to combine values from multiple sources (query string, headers, route values, form data, etc.) into a single object.</li>
<li>You need custom parsing or validation logic during binding.</li>
<li>You want to keep endpoint handlers clean by moving binding logic into the model itself.</li>
</ul>
<p>This pattern is especially helpful for reusable request models like paging, filtering, search criteria, or authentication-related context.</p>
<p>The syntax is given below:</p>



<pre class="wp-block-code"><code>public static ValueTask&lt;PagingData?&gt; BindAsync(
    HttpContext context,
    ParameterInfo parameter)</code></pre>



<p>This method is a special convention recognized by Minimal APIs. Whenever a parameter of type PagingData is needed, ASP.NET Core calls this method. Think of it as if the framework internally does:</p>



<pre class="wp-block-code"><code>PagingData pageData = await PagingData.BindAsync(context, parameter);</code></pre>



<p>The parameter <u>HttpContext</u> context contains everything about the request. You can access.</p>



<pre class="wp-block-code"><code>context.Request.Query
context.Request.Headers
context.Request.RouteValues
context.Request.Body
context.Request.Form</code></pre>



<p>The <u>ParameterInfo</u> parameter describes the endpoint parameter being bound. Here the pageData will provides metadata such as:</p>



<pre class="wp-block-code"><code>parameter.Name
parameter.ParameterType
parameter.Attributes</code></pre>



<p>The following code displays SortBy:price, SortDirection:Desc, CurrentPage:10 with the URI /products?SortBy=price&#038;SortDir=Desc&#038;Page=10:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using System.Reflection;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// GET /products?SortBy=price&SortDir=Desc&amp;Page=10
app.MapGet(&quot;/products&quot;, (PagingData pageData) =&gt; $&quot;SortBy:{pageData.SortBy}, &quot; +
       $&quot;SortDirection:{pageData.SortDirection}, CurrentPage:{pageData.CurrentPage}&quot;);

app.Run();

public class PagingData
{
    public string? SortBy { get; init; }
    public SortDirection SortDirection { get; init; }
    public int CurrentPage { get; init; } = 1;

    public static ValueTask&lt;PagingData?&gt; BindAsync(HttpContext context,
                                                   ParameterInfo parameter)
    {
        const string sortByKey = &quot;sortBy&quot;;
        const string sortDirectionKey = &quot;sortDir&quot;;
        const string currentPageKey = &quot;page&quot;;

        Enum.TryParse&lt;SortDirection&gt;(context.Request.Query&#x5B;sortDirectionKey],
                                     ignoreCase: true, out var sortDirection);
        int.TryParse(context.Request.Query&#x5B;currentPageKey], out var page);
        page = page == 0 ? 1 : page;

        var result = new PagingData
        {
            SortBy = context.Request.Query&#x5B;sortByKey],
            SortDirection = sortDirection,
            CurrentPage = page
        };

        return ValueTask.FromResult&lt;PagingData?&gt;(result);
    }
}

public enum SortDirection
{
    Default,
    Asc,
    Desc
}
</pre></div>


<h3>Reading from the Request body and Binding to a complex type</h3>
<p>The below code reads from the request body json and binds to a complex Work.cs class. The output returns the work class in json. See the below code.</p>



<pre class="wp-block-code"><code>app.MapPost("/", async (HttpContext context) =&gt; {
    if (context.Request.HasJsonContentType()) {
        var work = await context.Request.ReadFromJsonAsync&lt;Work&gt;();
        return Results.Ok(work);
    }
    else {
        return Results.BadRequest();
    }
});

class Work
{
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}</code></pre>



<p>If the request body contains the following JSON:</p>



<pre class="wp-block-code"><code>{"nameField":"Walk dog", "isComplete":false}</code></pre>



<p>The endpoint returns the following JSON:</p>



<pre class="wp-block-code"><code>{
    "name":"Walk dog",
    "isComplete":false
}</code></pre>



<div class="note">Conclusion</div>
<p>Parameter binding is one of the core features that makes ASP.NET Core Minimal APIs both concise and powerful. Throughout this guide, you&#8217;ve seen how the framework automatically binds values from route parameters, query strings, headers, forms, services, and request bodies, as well as how to customize the binding process using attributes like FromRoute, FromQuery, FromHeader, FromForm, FromBody, FromServices, AsParameters, and the BindAsync convention for complex scenarios. Understanding these binding mechanisms allows you to design cleaner endpoints, reduce boilerplate code, and encapsulate request parsing logic into reusable models. By mastering parameter binding, you&#8217;ll be able to build Minimal APIs that are easier to read, maintain, and extend while taking full advantage of the flexibility and performance.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-minimal-api-parameter-binding/">ASP.NET Core Minimal API Parameter Binding &#8211; full guide with codes</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-minimal-api-parameter-binding/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to Import Export Excel file in ASP.NET Core</title>
		<link>https://www.yogihosting.com/aspnet-core-import-export-excel/</link>
					<comments>https://www.yogihosting.com/aspnet-core-import-export-excel/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Mon, 08 Jun 2026 03:37:26 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22903</guid>

					<description><![CDATA[<p>An Excel file is a type of spreadsheet document created using Microsoft Excel, designed to organize, analyze, and store data in a structured format. It consists of worksheets made up of rows and columns, where users can input data, perform calculations using formulas, and create charts or graphs. Excel files are widely used in business, [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-import-export-excel/">How to Import Export Excel file in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>An Excel file is a type of spreadsheet document created using Microsoft Excel, designed to organize, analyze, and store data in a structured format. It consists of worksheets made up of rows and columns, where users can input data, perform calculations using formulas, and create charts or graphs. Excel files are widely used in business, education, and personal tasks because they make it easy to manage data, automate calculations, and visualize information effectively.</p>
<div class="starBlock">In this tutorial we are going to learn how to Import and Export data from an Excel file in ASP.NET Core. The whole source codes of this tutorial can be downloaded from my <a href="https://github.com/yogyogi/PDF-Excel-CSV-ASP.NET-Core" target="_blank">GitHub repository</a>. I will also be providing an excel file in the repo that you can use to test the import excel feature.</div>
<h2>ASP.NET CORE &#8211; Import Excel file records to SQL Server Database</h2>
<p>I have an Excel file containing employee data. The employee fields are Name, Designation, Salary and DOB. The excel file is shown by the below image. You can download this excel file from the GitHub repository itself.</p>



<span id="more-22903"></span>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/excel-file-employee.png" class="img-fluid" alt="Excel File Exmployee" title="Excel File Exmployee"></p>
<p>First I have to configure the app for Entity Framework core. I will need it to import the excel&#8217;s data to the database. So I create the <span class="term">Employee.cs</span> entity class with the same fields as given in the excel file. These are Name, Designation, Salary and DOB.</p>



<pre class="wp-block-code"><code>public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Designation { get; set; }
    public Double Salary { get; set; }
    public DateTime DOB { get; set; }
}</code></pre>



<h3>Installing Entity Framework Core</h3>
<p>Next, I install the 3 EF Core packages:</p>



<pre class="wp-block-code"><code>Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.Tools</code></pre>



<p>After that, add DbContext file called <span class="term">CompanyContext.cs</span> to the app with the following code.</p>



<pre class="wp-block-code"><code>public class CompanyContext : DbContext
{
    public CompanyContext(DbContextOptions&lt;CompanyContext&gt; options) : base(options)
    {
    }
    public DbSet&lt;Employee&gt; Employee { get; set; }
}</code></pre>



<p>On the <span class="term">appsettings.json</span> file, add the database connection string like given below.</p>



<pre class="wp-block-code"><code>"ConnectionStrings": {
  "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Company;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
}</code></pre>



<p class="wp-block-paragraph">Finally, register the DbContext in the program class as shown below.</p>



<pre class="wp-block-code"><code>builder.Services.AddDbContext&lt;CompanyContext&gt;(options =&gt;
  options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));</code></pre>



<p>Now run EF Core Migrations by executing the following commands one by one.</p>



<pre class="wp-block-code"><code>add-migration Migration1
Update-Database</code></pre>



<h2>Two methods to Read Excel in ASP.NET Core</h2>
<p>In order to import excel file&#8217;s data to the database, we have to first read it. There are 2 methods to read an excel file in ASP.NET Core, these are:</p>
<ol>
<li><b>System.Data.OleDb library</b>: it is an old method but works very well in reading all types of excel and csv files.</li>
<li><b>DocumentFormat.OpenXml library</b>:it is new library for working with Office Word, Excel, and PowerPoint documents. </li>
</ol>
<p>Both these libraries are extremely good and maintained by Microsoft so you are not going to face any issues with them. We are going to implement each of these 2 libraries one by one.</p>
<h3>Import Excel from System.Data.OleDb</h3>
<p>Firstly, install the <span class="term">System.Data.OleDb</span> library package from NuGet.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/system-data-oledb.png" alt="System.Data.OleDb" title="System.Data.OleDb Package Install from NuGet" class="img-fluid"></p>
<p>Run the following NuGet command to install this provider.</p>



<pre class="wp-block-code"><code>Install-Package System.Data.OleDb</code></pre>



<p>With the library installed let&#8217;s move to the controller part. Create a new controller called <span class="term">ExcelController.cs</span>. In this controller I will add the code for importing excel file&#8217;s data.</p>
<p>Start by injecting &#8220;IWebHostEnvironment&#8221; and &#8220;CompanyContext&#8221; on the constructor. IWebHostEnvironment will be needed for reading the &#8220;wwwroot&#8221; folder and CompanyContext is used for performing EF core operations. Remember that the excel file will first be uploaded to <span class="term">wwwroot/Excel</span> folder and then will be read. Check the below code:</p>



<pre class="wp-block-code"><code>private IWebHostEnvironment hostingEnvironment;
private CompanyContext context;

public ExcelController(IWebHostEnvironment environment, CompanyContext context)
{
    this.context = context;
    hostingEnvironment = environment;
}</code></pre>



<p>After this add action method called &#8220;ImportExcel&#8221; to the controller. This action method will perform the Import operation. See the code given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public IActionResult ImportExcel()
{
    return View();
}

&#x5B;HttpPost]
public async Task&lt;IActionResult&gt; ImportExcel(IFormFile excelfile)
{
    // By old OleDbConnection way

    string path = Path.Combine(hostingEnvironment.WebRootPath, &quot;Excel/&quot; + excelfile.FileName);
    using (var stream = new FileStream(path, FileMode.Create))
    {
        await excelfile.CopyToAsync(stream);
    }

    string connectionString = string.Empty;
    connectionString = string.Format(&quot;Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=&#039;Excel 12.0 Xml;HDR=YES;&#039;&quot;, path);

    using (var conn = new OleDbConnection(connectionString))
    {
        conn.Open();

        string tableName = conn.GetSchema(&quot;Tables&quot;).Rows&#x5B;0]&#x5B;&quot;TABLE_NAME&quot;].ToString();

        var query = $&quot;SELECT * FROM &#x5B;{tableName}]&quot;; // The file name is used in the query
        using (var adapter = new OleDbDataAdapter(query, conn))
        {
            var dataTable = new DataTable();
            adapter.Fill(dataTable);

            List&lt;Employee&gt; records = dataTable.AsEnumerable().Select(row =&gt; new Employee
            {
                Name = row.Field&lt;string&gt;(&quot;Name&quot;),// Use .Field&lt;T&gt;() for type safety and null handling
                Designation = row.Field&lt;string&gt;(&quot;Designation&quot;),
                Salary = row.Field&lt;Double&gt;(&quot;Salary&quot;),
                DOB = DateTime.Parse(row.Field&lt;string&gt;(&quot;DOB&quot;))
            }).ToList();

            context.AddRange(records);
            context.SaveChanges();
            ViewBag.Result = &quot;Import Successful&quot;;
        }
    }

    return View();
}
</pre></div>


<div class="note">Explanation</div>
<p>The action method of type Post receives the excel file uploaded from the view. I then save this file inside the <span class="term">wwwroot/Excel</span> folder by using the IWebHostEnvironment object.</p>



<pre class="wp-block-code"><code>string path = Path.Combine(hostingEnvironment.WebRootPath, "Excel/" + excelfile.FileName);
using (var stream = new FileStream(path, FileMode.Create))
{
    await excelfile.CopyToAsync(stream);
}</code></pre>



<p>Next, with <span class="term">System.Data.OleDb</span> provider the excel file is read.</p>



<pre class="wp-block-code"><code>string connectionString = string.Empty;
connectionString = string.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties='Excel 12.0 Xml;HDR=YES;'", path);</code></pre>



<p>The reading is done by the select query:</p>



<pre class="wp-block-code"><code>string tableName = conn.GetSchema("Tables").Rows&#91;0]&#91;"TABLE_NAME"].ToString();
var query = $"SELECT * FROM &#91;{tableName}]";</code></pre>



<p>The data returned by the select query is filled to a DataTable using SqlDataAdapter. Once the DataTable is filled, it is enumerated to fill the data in a List of Employees and then finally the EF Core <span class="term">AddRange()</span> method is used to insert the data to the database table. See the below code to understand it&#8217;s working.</p>



<pre class="wp-block-code"><code>var query = $"SELECT * FROM &#91;{tableName}]"; // The file name is used in the query
using (var adapter = new OleDbDataAdapter(query, conn))
{
    var dataTable = new DataTable();
    adapter.Fill(dataTable);

    List&lt;Employee&gt; records = dataTable.AsEnumerable().Select(row =&gt; new Employee
    {
        Name = row.Field&lt;string&gt;("Name"),// Use .Field&lt;T&gt;() for type safety and null handling
        Designation = row.Field&lt;string&gt;("Designation"),
        Salary = row.Field&lt;Double&gt;("Salary"),
        DOB = DateTime.Parse(row.Field&lt;string&gt;("DOB"))
    }).ToList();

    context.AddRange(records);
    context.SaveChanges();
    ViewBag.Result = "Import Successful";
}</code></pre>



<p>Create a new razor view file called <span class="term">ImportExcel.cshtml</span> with the following code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
@{
    ViewData&#x5B;&quot;Title&quot;] = &quot;Import Excel&quot;;
}

&lt;h1 class=&quot;bg-info text-white&quot;&gt;Import Excel&lt;/h1&gt;
&lt;a asp-controller=&quot;Home&quot; asp-action=&quot;Index&quot; class=&quot;btn btn-secondary&quot;&gt;Back&lt;/a&gt;
&lt;h2 class=&quot;bg-success text-white&quot;&gt;@ViewBag.Result&lt;/h2&gt;
&lt;form method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;
    &lt;div class=&quot;form-group&quot;&gt;
        &lt;label&gt;Select Excel File&lt;/label&gt;
        &lt;input type=&quot;file&quot; name=&quot;excelfile&quot; class=&quot;form-control&quot; /&gt;
    &lt;/div&gt;
    &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Import&lt;/button&gt;
&lt;/form&gt;
</pre></div>


<p>The view will present the form with a file control to upload the excel file. Once uploaded the file&#8217;s data is read and inserted to the database. See the below image which shows this form.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/import-excel-form.png" alt="Import Excel Form" title="Import Excel Form" class="img-fluid"></p>
<h3>Import Excel from DocumentFormat.OpenXml</h3>
<p>Now I will use OpenXml library to read excel file and import it&#8217;s data to a SQL Database. So first I have to install OpenXml library from NuGet.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/DocumentFormat.OpenXml.png" alt="DocumentFormat.OpenXml" title="DocumentFormat.OpenXml" class="img-fluid"></p>
<p>Running the below command can directly install this library to my app.</p>



<pre class="wp-block-code"><code>Install-Package System.Data.OleDb</code></pre>



<p>Important classes and their roles are:</p>
<ul>
<li><b>SpreadsheetDocument</b>: In the Open XML SDK, the SpreadsheetDocument class represents an Excel document package. It is the top-level container used to create, open, and manipulate spreadsheet files (typically .xlsx) programmatically without requiring Microsoft Office to be installed.</li>
<li><b>WorkbookPart</b>: it is the central container for an Excel spreadsheet document. It acts as the root for all worksheet-related data, global settings, and shared components.</li>
<li><b>WorksheetPart</b>: this class represents the part of a spreadsheet document package that contains all data and characteristics for a single worksheet. It acts as a container for the actual Worksheet element and its related parts.</li>
<li><b>OpenXmlReader</b>: it is an abstract base class used to read Office Open XML (OOXML) documents—such as .docx, .xlsx, and .pptx—using a SAX-like (Simple API for XML) approach.</li>
</ul> 
<p>Using these above classes in the code we can read the excel file. So add the <span class="term">ImportExcelOpenXml</span> action method with the following code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public IActionResult ImportExcelOpenXml()
{
    return View();
}

&#x5B;HttpPost]
public async Task&lt;IActionResult&gt; ImportExcelOpenXml(IFormFile excelfile)
{
    string path = Path.Combine(hostingEnvironment.WebRootPath, &quot;Excel/&quot; + excelfile.FileName);
    using (var stream = new FileStream(path, FileMode.Create))
    {
        await excelfile.CopyToAsync(stream);
    }

    List&lt;Employee&gt; empList = new List&lt;Employee&gt;();

    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, false))
    {
        WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart ?? spreadsheetDocument.AddWorkbookPart();
        WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
        OpenXmlReader reader = OpenXmlReader.Create(worksheetPart);

        string&#x5B;] header = { &quot;Name&quot;, &quot;Designation&quot;, &quot;Salary&quot;, &quot;DOB&quot; };
        Employee emp = new Employee();
        int counter = 1;

        while (reader.Read())
        {
            string current = reader.GetText();
            if ((current != &quot;&quot;) &&amp; (!header.Any(current.Contains)))
            {
                if (counter % 4 == 1)
                {
                    emp.Name = current;
                }
                else if (counter % 4 == 2)
                {
                    emp.Designation = current;
                }
                else if (counter % 4 == 3)
                {
                    emp.Salary = double.Parse(current);
                }
                else
                {
                    emp.DOB = Convert.ToDateTime(current);
                    empList.Add(emp);
                    emp = new Employee();
                }
                counter++;
            }
        }
    }

    context.AddRange(empList);
    context.SaveChanges();
    ViewBag.Result = &quot;Import Successful&quot;;

    return View();
}
</pre></div>


<div class="note">Explanation</div>
<p>The excel file is uploaded to the &#8220;wwwroot/Excel&#8221; folder by the code:</p>



<pre class="wp-block-code"><code>string path = Path.Combine(hostingEnvironment.WebRootPath, "Excel/" + excelfile.FileName);
using (var stream = new FileStream(path, FileMode.Create))
{
    await excelfile.CopyToAsync(stream);
}</code></pre>



<p>With the <span class="term">SpreadsheetDocument</span> class, the excel file is read from the &#8220;wwwroot/Excel&#8221; folder.</p>



<pre class="wp-block-code"><code>using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, false))
{
}</code></pre>



<p>An OpenXmlReader class is used to read the records in the excel file in cell by cell manner. I also have to use WorkbookPart and WorksheetPart to read the excel&#8217;s first sheet before.</p>



<pre class="wp-block-code"><code>WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart ?? spreadsheetDocument.AddWorkbookPart();
WorksheetPart worksheetPart = workbookPart.WorksheetParts.First();
OpenXmlReader reader = OpenXmlReader.Create(worksheetPart);</code></pre>



<p>I use my own custom logic in order to add all the records of the excel file in a list of employee object. To understand this code, see a string array class object &#8211; &#8220;header&#8221; for storing the header names of employee records.</p>



<pre class="wp-block-code"><code>string&#91;] header = { "Name", "Designation", "Salary", "DOB" };</code></pre>



<p>And a counter that starts from 1. I then uses them in the while loop, which is reading the cells of the excel one by one, to check if the current cell is not containing any of the header string and also the cell it not empty. This means the cell value is for either the Name, Designation, Salary or DOB field.</p>
<p>I then use counter % 4 to find out if the value is 1 which is for &#8220;Name&#8221; field, value of 2 for &#8220;Designation&#8221; field, value of 3 for &#8220;Salary&#8221; and value of 4 for &#8220;DOB&#8221; field.</p>
<p>See the below code.</p>



<pre class="wp-block-code"><code>while (reader.Read())
{
    string current = reader.GetText();
    if ((current != "") &amp;&amp; (!header.Any(current.Contains)))
    {
        if (counter % 4 == 1)
        {
            emp.Name = current;
        }
        else if (counter % 4 == 2)
        {
            emp.Designation = current;
        }
        else if (counter % 4 == 3)
        {
            emp.Salary = double.Parse(current);
        }
        else
        {
            emp.DOB = Convert.ToDateTime(current);
            empList.Add(emp);
            emp = new Employee();
        }
        counter++;
    }
}</code></pre>



<p>With the List of Employees now containing all the employee records, I am using EF Core <span class="term">AddRange()</span> method to insert this to the database. This way the import work is done.</p>



<pre class="wp-block-code"><code>context.AddRange(empList);
context.SaveChanges();
ViewBag.Result = "Import Successful";</code></pre>



<p>Next, add the razor view file called <span class="term">ImportExcelOpenXml.cshtml</span> which will contain a file upload control for uploading and reading the excel file. The code is given below.</p>



<pre class="wp-block-code"><code>@{
    ViewData&#91;"Title"] = "Import Excel";
}

&lt;h1 class="bg-info text-white"&gt;Import Excel (by OpenXml)&lt;/h1&gt;
&lt;a asp-controller="Home" asp-action="Index" class="btn btn-secondary"&gt;Back&lt;/a&gt;
&lt;h2 class="bg-success text-white"&gt;@ViewBag.Result&lt;/h2&gt;
&lt;form method="post" enctype="multipart/form-data"&gt;
    &lt;div class="form-group"&gt;
        &lt;label&gt;Select Excel File&lt;/label&gt;
        &lt;input type="file" name="excelfile" class="form-control" /&gt;
    &lt;/div&gt;
    &lt;button type="submit" class="btn btn-primary"&gt;Import&lt;/button&gt;
&lt;/form&gt;</code></pre>



<p>Run the app, you will be presented with a file upload control. Select the excel file whose data is to be imported to the database.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/import-excel-form.png" alt="Import Excel Form" title="Import Excel Form" class="img-fluid"></p>
<h2>ASP.NET CORE &#8211; Export Excel file records from SQL Server Database</h2>
<p>I will now perform the export of Employee records from the SQL Server database to an Excel file. I will show all the Employees in an HTML Table. This HTML table will have a checkbox against each record, for letting users to select the employees whose data needs to be saved to the excel file. After the employees are selected, the user clicks a button to generate this excel file. The excel file will be downloaded by the browser.</p>
<p>The below image shows this table with checkboxes:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/Export-excel-aspnet-core.png" alt="Export Excel ASP.NET Core" title="Export Excel ASP.NET Core" class="img-fluid"></p>
<p>I will have to add a new ViewModel called <span class="term">EmployeeViewModel.cs</span>. This is the same like Employee.cs class except that a new property &#8220;IsChecked&#8221; is added for the checkboxes. Through these checkboxes, user can select the records that needs to be exported to an excel file.</p>



<pre class="wp-block-code"><code>public class EmployeeViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Designation { get; set; }
    public Double Salary { get; set; }
    public DateTime DOB { get; set; }
    public bool IsChecked { get; set; }
}</code></pre>



<p>Add a new action method called &#8220;ExportExcel&#8221; to the controller. This action method reads the employee records from the database and returns them to the view where they will be displayed in an HTML Table.</p>



<pre class="wp-block-code"><code>public IActionResult ExportExcel()
{
    List&lt;Employee&gt; eList = context.Employee.ToList();

    List&lt;EmployeeViewModel&gt; records = eList.AsEnumerable().Select(row =&gt; new EmployeeViewModel
    {
        Id = row.Id,
        Name = row.Name,
        Designation = row.Designation,
        Salary = row.Salary,
        DOB = row.DOB
    }).ToList();

    return View(records);
}</code></pre>



<p>Next, add the razor view <span class="term">ExportExcel.cshtml</span> which shows the employees in a table along with a checkbox to make the selection. The code is given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
@{
    ViewData&#x5B;&quot;Title&quot;] = &quot;Export Excel&quot;;
}
@model List&lt;EmployeeViewModel&gt;

&lt;h1 class=&quot;bg-info text-white&quot;&gt;Export Excel&lt;/h1&gt;
  
&lt;a asp-controller=&quot;Home&quot; asp-action=&quot;Index&quot; class=&quot;btn btn-secondary&quot;&gt;Back&lt;/a&gt;

&lt;form method=&quot;post&quot;&gt;
    &lt;table class=&quot;table table-sm table-bordered&quot;&gt;
        &lt;tr&gt;
            &lt;th&gt;Id&lt;/th&gt;
            &lt;th&gt;Name&lt;/th&gt;
            &lt;th&gt;Designation&lt;/th&gt;
            &lt;th&gt;Salary&lt;/th&gt;
            &lt;th&gt;Date of Birth&lt;/th&gt;
            &lt;th&gt;&lt;/th&gt;
        &lt;/tr&gt;

        @for (int i = 0; i &lt; Model.Count; i++)
        {
            &lt;tr&gt;
                &lt;td&gt;
                    &lt;label asp-for=&quot;@Model&#x5B;i].Id&quot;&gt;@Model&#x5B;i].Id&lt;/label&gt;
                    &lt;input type=&quot;hidden&quot; asp-for=&quot;@Model&#x5B;i].Id&quot; /&gt;
                &lt;/td&gt;
                &lt;td&gt;&lt;label asp-for=&quot;@Model&#x5B;i].Name&quot;&gt;@Model&#x5B;i].Name&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;label asp-for=&quot;@Model&#x5B;i].Name&quot;&gt;@Model&#x5B;i].Designation&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;label asp-for=&quot;@Model&#x5B;i].Name&quot;&gt;@Model&#x5B;i].Salary&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;label asp-for=&quot;@Model&#x5B;i].Name&quot;&gt;@Model&#x5B;i].DOB&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;input type=&quot;checkbox&quot; asp-for=&quot;@Model&#x5B;i].IsChecked&quot; /&gt;&lt;/td&gt;
            &lt;/tr&gt;
        }
    &lt;/table&gt;
    &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Export Excel&lt;/button&gt;
&lt;/form&gt;
</pre></div>


<p>In the above code the html table is created to show all the employee records. Also note the checkbox given against each employee to enable user to select the employee records.</p>



<pre class="wp-block-code"><code>&lt;input type="checkbox" asp-for="@Model&#91;i].IsChecked" /&gt;</code></pre>



<p>A hidden field is also added which will transfer the Ids of employees to the post action method in the controller.</p>



<pre class="wp-block-code"><code>&lt;input type="hidden" asp-for="@Model&#91;i].Id" /&gt;</code></pre>



<p>Next, add the ExportExcel action of type POST. This action method will perform the export procedure. See the below code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpPost]
public IActionResult ExportExcel(List&lt;EmployeeViewModel&gt; empList)
{
    var selectedRecords = empList.Where(r =&gt; r.IsChecked).Select(r =&gt; r.Id).ToList();
    var emp = context.Employee.Where(o =&gt; selectedRecords.Contains(o.Id)).ToList();

    string path = Path.Combine(hostingEnvironment.WebRootPath, &quot;Excel/mydata.xlsx&quot;);

    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook))
    {

        // Add a WorkbookPart to the document.
        WorkbookPart workbookPart = spreadsheetDocument.AddWorkbookPart();
        workbookPart.Workbook = new Workbook();

        // Add a WorksheetPart to the WorkbookPart.
        WorksheetPart worksheetPart = workbookPart.AddNewPart&lt;WorksheetPart&gt;();
        worksheetPart.Worksheet = new Worksheet(new SheetData());

        SheetData sheetData = worksheetPart.Worksheet.GetFirstChild&lt;SheetData&gt;();

        // Add Sheets to the Workbook.
        Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());

        // Append a new worksheet and associate it with the workbook.
        Sheet sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart), SheetId = 1, Name = &quot;mySheet&quot; };
        sheets.Append(sheet);

        // Add Data
        Row row = new Row();
        row.Append(new Cell() { CellValue = new CellValue(&quot;Id&quot;), DataType = CellValues.String });
        row.Append(new Cell() { CellValue = new CellValue(&quot;Name&quot;), DataType = CellValues.String });
        row.Append(new Cell() { CellValue = new CellValue(&quot;Destination&quot;), DataType = CellValues.String });
        row.Append(new Cell() { CellValue = new CellValue(&quot;Salary&quot;), DataType = CellValues.String });
        row.Append(new Cell() { CellValue = new CellValue(&quot;DOB&quot;), DataType = CellValues.String });
        sheetData.Append(row);

        foreach (var e in emp)
        {
            row = new Row();
            row.Append(new Cell() { CellValue = new CellValue(e.Id), DataType = CellValues.Number });
            row.Append(new Cell() { CellValue = new CellValue(e.Name), DataType = CellValues.String });
            row.Append(new Cell() { CellValue = new CellValue(e.Designation), DataType = CellValues.String });
            row.Append(new Cell() { CellValue = new CellValue(e.Salary), DataType = CellValues.String });
            row.Append(new Cell() { CellValue = new CellValue(e.DOB), DataType = CellValues.Date });
            sheetData.Append(row);
        }

        workbookPart.Workbook.Save();
    }

    var contentType = &quot;application/octet-stream&quot;;
    return PhysicalFile(path, contentType, Path.GetFileName(path));
}
</pre></div>


<div class="note">Explanation</div>
<p>To understand the above code, first I am finding the checked employee ids and reading these employee records in a list of employee object by the name &#8220;emp&#8221;.</p>



<pre class="wp-block-code"><code>var selectedRecords = empList.Where(r =&gt; r.IsChecked).Select(r =&gt; r.Id).ToList();
var emp = context.Employee.Where(o =&gt; selectedRecords.Contains(o.Id)).ToList();</code></pre>



<p>Next, with <span class="term">SpreadsheetDocument</span> class, I am creating an empty excel file which will contain the checked employee records data.</p>



<pre class="wp-block-code"><code>using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(path, SpreadsheetDocumentType.Workbook))
{
}</code></pre>



<p>You will find the <u>WorkbookPart and WorksheetPart</u> that are adding a sheet to the excel file.</p>



<pre class="wp-block-code"><code>WorkbookPart workbookPart = spreadsheetDocument.AddWorkbookPart();
workbookPart.Workbook = new Workbook();

WorksheetPart worksheetPart = workbookPart.AddNewPart&lt;WorksheetPart&gt;();
worksheetPart.Worksheet = new Worksheet(new SheetData());</code></pre>



<p>Next, with the <u>SheetData and Sheet</u> classes the employee data is appended to this new sheet.</p>



<pre class="wp-block-code"><code>SheetData sheetData = worksheetPart.Worksheet.GetFirstChild&lt;SheetData&gt;();
Sheets sheets = workbookPart.Workbook.AppendChild(new Sheets());

Sheet sheet = new Sheet() { Id = workbookPart.GetIdOfPart(worksheetPart), SheetId = 1, Name = "mySheet" };
sheets.Append(sheet);</code></pre>



<p>I now use the &#8220;Row&#8221; class to add the Employee headers which are Id, Name, Destination, Salary, and DOB. And then looping through all the records that are there in the list of employee objects, and adding them to the row. The row is appended to the sheet with the <span class="code">sheetData.Append(row)</span> method.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
Row row = new Row();
 row.Append(new Cell() { CellValue = new CellValue(&quot;Id&quot;), DataType = CellValues.String });
 row.Append(new Cell() { CellValue = new CellValue(&quot;Name&quot;), DataType = CellValues.String });
 row.Append(new Cell() { CellValue = new CellValue(&quot;Destination&quot;), DataType = CellValues.String });
 row.Append(new Cell() { CellValue = new CellValue(&quot;Salary&quot;), DataType = CellValues.String });
 row.Append(new Cell() { CellValue = new CellValue(&quot;DOB&quot;), DataType = CellValues.String });
 sheetData.Append(row);

 foreach (var e in emp)
 {
     row = new Row();
     row.Append(new Cell() { CellValue = new CellValue(e.Id), DataType = CellValues.Number });
     row.Append(new Cell() { CellValue = new CellValue(e.Name), DataType = CellValues.String });
     row.Append(new Cell() { CellValue = new CellValue(e.Designation), DataType = CellValues.String });
     row.Append(new Cell() { CellValue = new CellValue(e.Salary), DataType = CellValues.String });
     row.Append(new Cell() { CellValue = new CellValue(e.DOB), DataType = CellValues.Date });
     sheetData.Append(row);
 }
</pre></div>


<p>At last the excel file is downloaded with the below code.</p>



<pre class="wp-block-code"><code>var contentType = "application/octet-stream";
return PhysicalFile(path, contentType, Path.GetFileName(path));</code></pre>



<p>Its now time to run the app and test the feature. I select the records no 1, 6, 8 and 10 and then click the button to generate the excel file. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/Export-excel-aspnet-core-openxml.png" alt="Export Excel ASP.NET Core OpenXML" title="Export Excel ASP.NET Core OpenXML" class="img-fluid"></p>
<p>The excel file is generated and is shown below.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/excel-file-download-openxml.png" alt="Excel File Download OpenXML" title="Excel File Download OpenXML" class="img-fluid"></p>
<div class="note">Conclusion</div>
<p>In this tutorial I created the full features of Import and Export excel in ASP.NET Core. I explained both the method of OpenXml and System.Data.OleDb to read the excel file. The full source codes of this tutorial is available in my GitHub repository. The link is given at the top.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-import-export-excel/">How to Import Export Excel file in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-import-export-excel/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to Import Export CSV file in ASP.NET Core</title>
		<link>https://www.yogihosting.com/aspnet-core-import-export-csv/</link>
					<comments>https://www.yogihosting.com/aspnet-core-import-export-csv/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Sat, 16 May 2026 10:49:18 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22894</guid>

					<description><![CDATA[<p>A CSV (Comma-Separated Values) file is a simple and widely used format for storing tabular data, where each line represents a row and each value is separated by a comma. It is commonly used for exchanging data between different programs, such as spreadsheets and databases, because it is lightweight and easy to read. In this [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-import-export-csv/">How to Import Export CSV file in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>A CSV (Comma-Separated Values) file is a simple and widely used format for storing tabular data, where each line represents a row and each value is separated by a comma. It is commonly used for exchanging data between different programs, such as spreadsheets and databases, because it is lightweight and easy to read.</p>



<span id="more-22894"></span>



<p>In this tutorial we are going to learn how to Import and Export data from a CSV file in ASP.NET Core. The whole source codes of this tutorial can be downloaded from my <a href="https://github.com/yogyogi/PDF-Excel-CSV-ASP.NET-Core" target="_blank">GitHub repository</a>.</p>
<h2>ASP.NET CORE &#8211; Import CSV file records to SQL Server Database</h2>
<p>I have a CSV file containing employee data. The employee fields are Name, Designation, Salary and DOB. The CSV file is shown by the below image. You can download this csv file from the GitHub repository itself.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/csv-file.png" alt="CSV File" title="CSV File" class="img-fluid"></p>
<p>Let&#8217;s import this CSV file in ASP.NET Core.</p>
<p>Firstly, I install the <span class="term">System.Data.OleDb</span> package from NuGet. It is the Data Provider for OLE DB data sources and will be used to read the CSV file.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/system-data-oledb.png" alt="System.Data.OleDb" title="System.Data.OleDb Package Install from NuGet" class="img-fluid"></p>
<p>Run the following NuGet command to install this provider.</p>



<pre class="wp-block-code"><code>Install-Package System.Data.OleDb</code></pre>



<p>Next, I create the <span class="term">Employee.cs</span> entity class with the same fields as given in the csv file. These are Name, Designation, Salary and DOB.</p>



<pre class="wp-block-code"><code>public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Designation { get; set; }
    public Double Salary { get; set; }
    public DateTime DOB { get; set; }
}</code></pre>



<h3>Installing Entity Framework Core</h3>
<p>Since I will insert the CSV file&#8217;s data to the SQL Server database therefore I will need Entity Framework Core. So install the 3 packages:</p>



<pre class="wp-block-code"><code>Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.Tools</code></pre>



<p>Next, add DbContext file called <span class="term">CompanyContext.cs</span> to the app with the following code.</p>



<pre class="wp-block-code"><code>public class CompanyContext : DbContext
{
    public CompanyContext(DbContextOptions&lt;CompanyContext&gt; options) : base(options)
    {
    }
    public DbSet&lt;Employee&gt; Employee { get; set; }
}</code></pre>



<p>To the <span class="term">appsettings.json</span> file, add the database connection string like given below.</p>



<pre class="wp-block-code"><code>"ConnectionStrings": {
  "DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=Company;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
}</code></pre>



<p>Register the DbContext in the program class as shown below.</p>



<pre class="wp-block-code"><code>builder.Services.AddDbContext&lt;CompanyContext&gt;(options =&gt;
  options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));</code></pre>



<p>With everything set up, perform the EF Core Migrations by running the following commands one by one.</p>



<pre class="wp-block-code"><code>add-migration Migration1
Update-Database</code></pre>



<h3>Create the View and the Controller</h3>
<p>Create  a new controller called <span class="term">CsvController.cs</span>. In this controller I will add the code for importing csv file&#8217;s data.</p>
<p>Firstly, inject &#8220;IWebHostEnvironment&#8221; and &#8220;CompanyContext&#8221; on the constructor. IWebHostEnvironment will be needed for reading the wwwroot folder and CompanyContext is used for performing EF core operations.</p>



<pre class="wp-block-code"><code>private IWebHostEnvironment hostingEnvironment;
private CompanyContext context;

public CsvController(IWebHostEnvironment environment, CompanyContext context)
{
    this.context = context;
    hostingEnvironment = environment;
}</code></pre>



<div class="starBlock">Want to create professional PDF files in .NET. Check my tutorial &#8211; <a href="https://www.yogihosting.com/aspnet-core-pdf-migradoc/">How to create PDF files in ASP.NET Core with MigraDoc</a>. Note that MigraDoc library is completely free.</div>
<p>After this add action method called &#8220;ImportCsv&#8221; to the controller. This action method will perform the CSV Import operation. See the code given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public IActionResult ImportCsv()
{
    return View();
}

&#x5B;HttpPost]
public async Task&lt;IActionResult&gt; ImportCsv(IFormFile csvfile)
{
    // By old OleDbConnection way

    string path = Path.Combine(hostingEnvironment.WebRootPath, &quot;CSV/&quot; + csvfile.FileName);
    using (var stream = new FileStream(path, FileMode.Create))
    {
        await csvfile.CopyToAsync(stream);
    }

    string folderPath = Path.Combine(hostingEnvironment.WebRootPath, &quot;CSV&quot;); 
    string connectionString = $@&quot;Provider=Microsoft.ACE.OLEDB.12.0;Data Source={folderPath};Extended Properties=&quot;&quot;text;HDR=YES;FMT=Delimited;IMEX=1;MaxScanRows=0&quot;&quot;&quot;;

    using (var conn = new OleDbConnection(connectionString))
    {
        conn.Open();
        var query = $&quot;SELECT * FROM &#x5B;{csvfile.FileName}]&quot;; // The file name is used in the query
        using (var adapter = new OleDbDataAdapter(query, conn))
        {
            var dataTable = new DataTable();
            adapter.Fill(dataTable);

            List&lt;Employee&gt; records = dataTable.AsEnumerable().Select(row =&gt; new Employee
            {
                Name = row.Field&lt;string&gt;(&quot;Name&quot;),// Use .Field&lt;T&gt;() for type safety and null handling
                Designation = row.Field&lt;string&gt;(&quot;Designation&quot;),
                Salary = row.Field&lt;Double&gt;(&quot;Salary&quot;),
                DOB = row.Field&lt;DateTime&gt;(&quot;DOB&quot;)
            }).ToList();

            context.AddRange(records);
            context.SaveChanges();
            ViewBag.Result = &quot;Import Successful&quot;;
        }
    }

    return View();
}
</pre></div>


<div class="note">Explanation</div>
<p>The action method of type Post receives the csv file uploaded from the view. I then save this csv file inside the <span class="term">wwwroot/CSV</span> folder by using the IWebHostEnvironment object.</p>



<pre class="wp-block-code"><code>string path = Path.Combine(hostingEnvironment.WebRootPath, "CSV/" + csvfile.FileName);
using (var stream = new FileStream(path, FileMode.Create))
{
    await csvfile.CopyToAsync(stream);
}</code></pre>



<p>Next, with <span class="term">System.Data.OleDb</span> provider the CSV file is read. Note that this csv file is stored inside the <span class="term">wwwroot/CSV</span> folder.</p>



<pre class="wp-block-code"><code>string folderPath = Path.Combine(hostingEnvironment.WebRootPath, "CSV");
string connectionString = $@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source={folderPath};Extended Properties=""text;HDR=YES;FMT=Delimited;IMEX=1;MaxScanRows=0""";</code></pre>



<p>The actual CSV reading code is given below where a select query &#8211; <span class="code">SELECT * FROM [{csvfile.FileName}]</span> is executed to read the CSV data. Then the data is filled to a DataTable using SqlDataAdapter.</p>
<p>Once the DataTable is filled, it is enumerated to fill the data in a List of Employees and then finally the EF Core <span class="term">AddRange()</span> method is used to insert the data to the database table. See the below code to understand it&#8217;s working.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using (var conn = new OleDbConnection(connectionString))
{
    conn.Open();
    var query = $&quot;SELECT * FROM &#x5B;{csvfile.FileName}]&quot;; // The file name is used in the query
    using (var adapter = new OleDbDataAdapter(query, conn))
    {
        var dataTable = new DataTable();
        adapter.Fill(dataTable);

        List&lt;Employee&gt; records = dataTable.AsEnumerable().Select(row =&gt; new Employee
        {
            Name = row.Field&lt;string&gt;(&quot;Name&quot;),// Use .Field&lt;T&gt;() for type safety and null handling
            Designation = row.Field&lt;string&gt;(&quot;Designation&quot;),
            Salary = row.Field&lt;Double&gt;(&quot;Salary&quot;),
            DOB = row.Field&lt;DateTime&gt;(&quot;DOB&quot;)
        }).ToList();

        context.AddRange(records);
        context.SaveChanges();
        ViewBag.Result = &quot;Import Successful&quot;;
    }
}
</pre></div>


<p>Create a new razor view file called <span class="term">ImportCsv.cshtml</span> with the following code.</p>



<pre class="wp-block-code"><code>@{
    ViewData&#91;"Title"] = "Import CSV";
}

&lt;h1 class="bg-info text-white"&gt;Import CSV&lt;/h1&gt;
&lt;a asp-controller="Home" asp-action="Index" class="btn btn-secondary"&gt;Back&lt;/a&gt;
&lt;h2 class="bg-success text-white"&gt;@ViewBag.Result&lt;/h2&gt;
&lt;form method="post" enctype="multipart/form-data"&gt;
    &lt;div class="form-group"&gt;
        &lt;label&gt;Select CSV File&lt;/label&gt;
        &lt;input type="file" name="csvfile" class="form-control" /&gt;
    &lt;/div&gt;
    &lt;button type="submit" class="btn btn-primary"&gt;Import&lt;/button&gt;
&lt;/form&gt;</code></pre>



<div class="note">Testing CSV Import</div>
<p>Let&#8217;s run the app to test the CSV import feature. The form will contain a file upload where I need to select the CSV file. Click the import button to perform the import task.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/import-csv-aspnet-core.png" alt="Import CSV ASP.NET Core" title="Import CSV ASP.NET Core" class="img-fluid"></p>
<p>Lets check the Employee table, where I can see all the data is successfully inserted to the table.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/csv-import-database.png" alt="CSV Import Database" title="CSV Import to Database" class="img-fluid"></p>
<p>So this successfully completes the CSV import feature in ASP.NET Core. Next, I will perform the CSV export in ASP.NET Core.</p>
<h2>ASP.NET CORE &#8211; Export CSV file records from SQL Server Database</h2>
<p>I will now perform the export of Employee records from the SQL Server database to a CSV file. First of all I will show all the Employees in an HTML Table. This HTML table will have a checkbox against each record, for enabling user to select the employees whose data needs to be saved to a CSV file. After the employees are selected, the user clicks a button to generate this CSV file.</p>
<p>The below image shows this:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/Export-CSV-aspnet-core.png" alt="Export CSV ASP.NET core" title="Export CSV in ASP.NET Core" class="img-fluid"></p>
<p>Start by adding a new ViewModel called <span class="term">EmployeeViewModel.cs</span>. This is the same like Employee.cs class except that a new property &#8220;IsChecked&#8221; is added for the checkboxes. Recall that I will be adding checkboxes against each record for enabling the user to select the records from the html table.</p>



<pre class="wp-block-code"><code>public class EmployeeViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Designation { get; set; }
    public Double Salary { get; set; }
    public DateTime DOB { get; set; }
    public bool IsChecked { get; set; }
}</code></pre>



<div class="starBlock">Interesting to read &#8211; <a href="https://www.yogihosting.com/entity-framework-extensions-bulk-operations/">The Entity Framework Extensions: Performance-Focused (Need for Speed) when working with large Datasets</a></div>
<p>Add a new action method called &#8220;ExportCsv&#8221; to the controller. This action method reads the employee records from the database and returns them to the view where they will be displayed in an HTML Table.</p>



<pre class="wp-block-code"><code>public IActionResult ExportCsv()
{
    List&lt;Employee&gt; eList = context.Employee.ToList();

    List&lt;EmployeeViewModel&gt; records = eList.AsEnumerable().Select(row =&gt; new EmployeeViewModel
    {
        Id = row.Id,
        Name = row.Name,
        Designation = row.Designation,
        Salary = row.Salary,
        DOB = row.DOB
    }).ToList();

    return View(records);
}</code></pre>



<p>Next, add the razor view file called <span class="term">ExportCsv.html</span> with the following code.</p>



<pre class="wp-block-code"><code>@{
    ViewData&#91;"Title"] = "Export CSV";
}

@model List&lt;EmployeeViewModel&gt;

&lt;h1 class="bg-info text-white"&gt;Export CSV&lt;/h1&gt;
  
&lt;a asp-controller="Home" asp-action="Index" class="btn btn-secondary"&gt;Back&lt;/a&gt;

&lt;form method="post"&gt;
    &lt;table class="table table-sm table-bordered"&gt;
        &lt;tr&gt;
            &lt;th&gt;Id&lt;/th&gt;
            &lt;th&gt;Name&lt;/th&gt;
            &lt;th&gt;Designation&lt;/th&gt;
            &lt;th&gt;Salary&lt;/th&gt;
            &lt;th&gt;Date of Birth&lt;/th&gt;
            &lt;th&gt;&lt;/th&gt;
        &lt;/tr&gt;

        @for (int i = 0; i &lt; Model.Count; i++)
        {
            &lt;tr&gt;
                &lt;td&gt;
                    &lt;label asp-for="@Model&#91;i].Id"&gt;@Model&#91;i].Id&lt;/label&gt;
                    &lt;input type="hidden" asp-for="@Model&#91;i].Id" /&gt;
                &lt;/td&gt;
                &lt;td&gt;&lt;label asp-for="@Model&#91;i].Name"&gt;@Model&#91;i].Name&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;label asp-for="@Model&#91;i].Name"&gt;@Model&#91;i].Designation&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;label asp-for="@Model&#91;i].Name"&gt;@Model&#91;i].Salary&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;label asp-for="@Model&#91;i].Name"&gt;@Model&#91;i].DOB&lt;/label&gt;&lt;/td&gt;
                &lt;td&gt;&lt;input type="checkbox" asp-for="@Model&#91;i].IsChecked" /&gt;&lt;/td&gt;
            &lt;/tr&gt;
        }
    &lt;/table&gt;
    &lt;button type="submit" class="btn btn-primary"&gt;Export&lt;/button&gt;
&lt;/form&gt;</code></pre>



<p>In the above code the html table is created to show all the employee records. Also note the checkbox given against each employee to enable user to select the employee records.</p>



<pre class="wp-block-code"><code>&lt;input type="checkbox" asp-for="@Model&#91;i].IsChecked" /&gt;</code></pre>



<p>Also notice a hidden field which will transfer the Ids of employees to the post action method in the controller.</p>



<pre class="wp-block-code"><code>&lt;input type="hidden" asp-for="@Model&#91;i].Id" /&gt;</code></pre>



<p>Next, add the ExportCsv action of type POST. This action method will perform the creation of CSV file from the selected employee records. See the below code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpPost]
public IActionResult ExportCsv(List&lt;EmployeeViewModel&gt; empList)
{
    var selectedRecords = empList.Where(r =&gt; r.IsChecked).Select(r =&gt; r.Id).ToList();
    var emp = context.Employee.Where(o =&gt; selectedRecords.Contains(o.Id)).ToList();

    var sb = new StringBuilder();

    sb.Append(&quot;Id&quot; + &#039;,&#039; + &quot;Name&quot; + &#039;,&#039; + &quot;Designation&quot; + &#039;,&#039; + &quot;Salary&quot; + &#039;,&#039; + &quot;DOB&quot;); // header
    sb.Append(&quot;\r\n&quot;); // New line after header

    foreach (var e in emp)
    {
        sb.Append(e.Id.ToString() + &#039;,&#039; + e.Name + &#039;,&#039; + e.Designation + &#039;,&#039; + e.Salary + &#039;,&#039; + e.DOB);
        sb.Append(&quot;\r\n&quot;);
    }

    return File(Encoding.UTF8.GetBytes(sb.ToString()), &quot;text/csv&quot;, &quot;exportdata.csv&quot;);
}
</pre></div>


<div class="note">Explanation</div>
<p>I first read all the employee Id that contains the IsChecked property value to be true. These are the selected employees.</p>



<pre class="wp-block-code"><code>var selectedRecords = empList.Where(r =&gt; r.IsChecked).Select(r =&gt; r.Id).ToList();</code></pre>



<p>Then with EF Core, I read these Id&#8217;s records from the database.</p>



<pre class="wp-block-code"><code>var emp = context.Employee.Where(o =&gt; selectedRecords.Contains(o.Id)).ToList();</code></pre>



<p>With the StringBuilder, I create a CSV file (which is comma separated), adding both the header row and the employee data of selected employees.</p>
<p>Then converting the data to a CSV file and downloading this file to the client browser from the memory.</p>



<pre class="wp-block-code"><code>return File(Encoding.UTF8.GetBytes(sb.ToString()), "text/csv", "exportdata.csv");</code></pre>



<p>I can now select the employee records for generating the CSV file. Lets say I select employee with Id 2 and 10. The generated CSV will contain only these 2 records. Check the generated CSV file image given below.</p> 
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/05/generated-csv-aspnet-core.png" alt="Generated CSV ASP.NET Core" title="Generated CSV ASP.NET Core" class="img-fluid"></p>
<div class="note">Conclusion</div>
<p>In the tutorial I created both the CSV import and export CSV feature in ASP.NET Core. I hope you understood each and every part of it. You can use the source codes of this tutorial by downloading the GitHub repository. The link is given at the top.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-import-export-csv/">How to Import Export CSV file in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-import-export-csv/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to create PDF files in ASP.NET Core with MigraDoc</title>
		<link>https://www.yogihosting.com/aspnet-core-pdf-migradoc/</link>
					<comments>https://www.yogihosting.com/aspnet-core-pdf-migradoc/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Thu, 30 Apr 2026 14:06:01 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[ASP.NET Core apps in Docker]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22877</guid>

					<description><![CDATA[<p>MigraDoc is a popular .NET library used in ASP.NET Core applications to generate structured PDF documents programmatically. It creates rich documents with elements like paragraphs, tables, headers, and images. In an ASP.NET Core project, you typically define a document using Migradoc’s object model, render it with PdfDocumentRenderer, and then return the generated PDF as a [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-pdf-migradoc/">How to create PDF files in ASP.NET Core with MigraDoc</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>MigraDoc is a popular .NET library used in ASP.NET Core applications to generate structured PDF documents programmatically. It creates rich documents with elements like paragraphs, tables, headers, and images. In an ASP.NET Core project, you typically define a document using Migradoc’s object model, render it with PdfDocumentRenderer, and then return the generated PDF as a file response from a controller. This approach is useful for creating invoices, reports, or dynamic documents on the fly, while keeping layout logic clean and maintainable within your C# code.</p>
<div class="starBlock">MigraDoc is 100% free and Open Source. You can use it in your projects freely. Download the source codes from our <a href="https://github.com/yogyogi/PDF-Excel-CSV-ASP.NET-Core" target="_blank">GitHub repository</a>.</div>



<span id="more-22877"></span>



<p>The given image explains the full process of PDF generation:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/pdf-generation-aspnet-core.jpg" alt="PDF Generation ASP.NET Core" title="PDF Generation ASP.NET Core" class="img-fluid"></p>
<p>Let&#8217;s generate a complete PDF file with MigraDoc in ASP.NET Core version 10.0</p>
<h2>MigraDoc Code Structure</h2>
<p>The structure of MigraDoc contains 3 parts:</p>
<ol>
<li>Document: it is the parent object which contains sections.</li>
<li>Section: all contents of a document are organized in sections. Sections contains other objects like table and paragraph.</li>
<li>PdfDocumentRenderer: it takes a Document object, formats it properly (layout, fonts, pages) and outputs a PDF file</li>
</ol>
<p>The code structure is given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var document = new Document();
var section = document.AddSection();

var heading = section.AddParagraph(&quot;Test PDF&quot;);

var pdfRenderer = new PdfDocumentRenderer();

pdfRenderer.Document = document;

pdfRenderer.RenderDocument();
pdfRenderer.Save(&quot;SimpleDocument.pdf&quot;);
</pre></div>


<h2>Generate PDF file with MigraDoc in ASP.NET Core version 10</h2>
<p>To see how MigraDoc works we will generate a complete credit card statement of a bank customer in ASP.NET Core version 10.0. Once completed the PDF file will look as shown in the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/migradoc-pdf-generation.png" alt="MigraDoc PDF Generation" title="MigraDoc PDF Generation" class="img-fluid"></p>
<p>In Visual Studio create a new project and select the template called ASP.NET Core Web App (Model-View-Controller).</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2022/02/ASP.NET-Core-Web-App-MVC.png" alt="asp.net core web app mvc template" title="asp.net core web app mvc template" class="img-fluid"></p>
<p>First, we need to install the package called <span class="term">PDFsharp-MigraDoc</span> from NuGet.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/PDFsharp-MigraDoc.png" alt="PDFsharp MigraDoc" title="PDFsharp MigraDoc" class="img-fluid"></p>
<p>Next, import the necessary namespaces on the controller.</p>



<pre class="wp-block-code"><code>using MigraDoc.Rendering;
using MigraDocTutorial.Models;
using PdfSharp.Fonts;
using MigraDoc.DocumentObjectModel;</code></pre>



<div class="starBlock">Import and export functionality in .NET is commonly used for transferring tabular data between applications, databases with CSV files (Comma-Separated Values). Kindly read my article &#8211; <a href="https://www.yogihosting.com/aspnet-core-import-export-csv/">How to Import Export CSV file in ASP.NET Core</a></div>
<p>Now, open the HomeController file and in Index action we add MigraDoc codes as shown below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class HomeController : Controller
{
    private IWebHostEnvironment hostingEnvironment;

    public HomeController(IWebHostEnvironment environment)
    {
        hostingEnvironment = environment;
    }

    public IActionResult Index()
    {
        string imagePath = Path.Combine(hostingEnvironment.WebRootPath, &quot;Images&quot;);

        var document = new Document();
        var section = document.AddSection();

        GlobalFontSettings.UseWindowsFontsUnderWindows = true;

        var heading = section.AddParagraph(&quot;Your Credit Card Statement Report has been Generated&quot;);

        heading.Format.OutlineLevel = OutlineLevel.Level1;
        heading.Format.Font.Size = 25;

        // Add line below the heading
        heading.Format.Borders.Bottom.Width = 1;
        heading.Format.SpaceAfter = &quot;30pt&quot;;

        // Add table.
        var table = section.AddTable();

        // Add first column.
        var columnA = table.AddColumn(Unit.FromCentimeter(6));

        // Add second column.
        var columnB = table.AddColumn(Unit.FromCentimeter(12));
        
        // Add first row.
        var row1 = table.AddRow();

        // Add paragraph to first cell of row1.
        var cellA1 = row1&#x5B;0];

        document.ImagePath = imagePath;
        var image = cellA1.AddImage(&quot;woman.jpg&quot;);
        image.Width = Unit.FromPoint(150);
        image.Height = Unit.FromPoint(150);

        // Add paragraph to second cell of row1.
        var cellB1 = row1&#x5B;1];
        cellB1.AddParagraph(&quot;Name: Mrs. Grace Kelly&quot;);
        cellB1.AddParagraph(&quot;Address: House 20, 31 drowning street, London (UK)&quot;);
        cellB1.AddParagraph(&quot;Occupation: Doctor&quot;);
        cellB1.AddParagraph(&quot;Age: 30&quot;);
        cellB1.Format.Font.Size = 25;
        cellB1.Format.Font.Color = Colors.Red;

        var heading1 = section.AddParagraph(&quot;This month&#039;s transation in your Credit Card !&quot;);
        heading1.Format.Font.Size = 20;
        heading1.Format.Font.Color = Colors.BurlyWood;

        // Add line below the heading
        heading1.Format.Borders.Bottom.Width = 1;
        heading1.Format.SpaceBefore = &quot;30pt&quot;;
        heading1.Format.SpaceAfter = &quot;30pt&quot;;

        var table2 = section.AddTable();
        table2.Borders.Visible = true;

        table2.AddColumn(&quot;3cm&quot;);
        table2.AddColumn(&quot;3cm&quot;);
        table2.AddColumn(&quot;3cm&quot;);
        table2.AddColumn(&quot;3cm&quot;);
        table2.AddColumn(&quot;3cm&quot;);

        var row1Table2 = table2.AddRow();
        row1Table2.HeadingFormat = true;
        row1Table2.Format.Font.Color = Colors.BlueViolet;
        row1Table2.Shading.Color = Colors.LightGray;

        row1Table2&#x5B;0].AddParagraph(&quot;S.No&quot;);
        row1Table2&#x5B;1].AddParagraph(&quot;Merchant&quot;);
        row1Table2&#x5B;2].AddParagraph(&quot;Item&quot;);
        row1Table2&#x5B;3].AddParagraph(&quot;Cost&quot;);
        row1Table2&#x5B;4].AddParagraph(&quot;Date&quot;);

        var row2Table2 = table2.AddRow();
        row2Table2&#x5B;0].AddParagraph(&quot;1&quot;);
        row2Table2&#x5B;1].AddParagraph(&quot;NYC Junction&quot;);
        row2Table2&#x5B;2].AddParagraph(&quot;Fruits&quot;);
        row2Table2&#x5B;3].AddParagraph(&quot;$100.00&quot;);
        row2Table2&#x5B;4].AddParagraph(&quot;June 1&quot;);

        var row3Table2 = table2.AddRow();
        row3Table2&#x5B;0].AddParagraph(&quot;2&quot;);
        row3Table2&#x5B;1].AddParagraph(&quot;David Store&quot;);
        row3Table2&#x5B;2].AddParagraph(&quot;Napkins&quot;);
        row3Table2&#x5B;3].AddParagraph(&quot;5.90&quot;);
        row3Table2&#x5B;4].AddParagraph(&quot;June 3&quot;);

        var row4Table2 = table2.AddRow();
        row4Table2&#x5B;0].AddParagraph(&quot;3&quot;);
        row4Table2&#x5B;1].AddParagraph(&quot;Singhs&quot;);
        row4Table2&#x5B;2].AddParagraph(&quot;Toys&quot;);
        row4Table2&#x5B;3].AddParagraph(&quot;$99.99&quot;);
        row4Table2&#x5B;4].AddParagraph(&quot;June 9&quot;);

        var row5Table2 = table2.AddRow();
        row5Table2&#x5B;0].AddParagraph(&quot;4&quot;);
        row5Table2&#x5B;1].AddParagraph(&quot;Seven 11&quot;);
        row5Table2&#x5B;2].AddParagraph(&quot;Grocery&quot;);
        row5Table2&#x5B;3].AddParagraph(&quot;$140.00&quot;);
        row5Table2&#x5B;4].AddParagraph(&quot;June 15&quot;);

        var row6Table2 = table2.AddRow();
        row6Table2&#x5B;0].AddParagraph(&quot;5&quot;);
        row6Table2&#x5B;1].AddParagraph(&quot;Carlos Pharmacy&quot;);
        row6Table2&#x5B;2].AddParagraph(&quot;Drugs&quot;);
        row6Table2&#x5B;3].AddParagraph(&quot;$60.00&quot;);
        row6Table2&#x5B;4].AddParagraph(&quot;June 25&quot;);

        var custName = section.AddParagraph(&quot;Hello Grace,&quot;);
        custName.Format.SpaceBefore = &quot;30pt&quot;;
        custName.Format.SpaceAfter = &quot;20pt&quot;;
        section.AddParagraph(&quot;Thank you for being our valuable customer. We hope our letter finds you in the best of health and wealth.\n\nYours Sincerely.\nICICI Bank&quot;);

        // Create a PDF renderer for the MigraDoc document.
        var pdfRenderer = new PdfDocumentRenderer();

        // Associate the MigraDoc document with a renderer.
        pdfRenderer.Document = document;

        // Layout and render document to PDF.
        pdfRenderer.RenderDocument();
        // Save the document.
        pdfRenderer.Save(&quot;SimpleDocument.pdf&quot;);

        return View();
    }
}
</pre></div>


<p>The above code is a complete code which will generate the Credit Card statement PDF file. Lets understand the code part by part.</p>
<h2>Document and Section</h2>
<p>In the above code we defined the Document and added a section to it. The code which does this work is given below.</p>



<pre class="wp-block-code"><code>var document = new Document();
var section = document.AddSection();</code></pre>



<p>We then specified MigraDoc to use windows fonts by the below code:</p>



<pre class="wp-block-code"><code>GlobalFontSettings.UseWindowsFontsUnderWindows = true;</code></pre>



<div class="starBlock">
<p>Since we will add the image of the customer on the PDF file so we need to inject IWebHostEnvironment on the constructor of the controller, in order to get the images from the wwwroot/Images folder. See the below code.</p>



<pre class="wp-block-code"><code>public HomeController(IWebHostEnvironment environment)
{
    hostingEnvironment = environment;
}

string imagePath = Path.Combine(hostingEnvironment.WebRootPath, "Images");</code></pre>



</div>
<h2>Heading</h2>
<p>From the <span class="term">AddParagraph</span> method we added the text &#8211; &#8220;Your Credit Card Statement Report has been Generated&#8221;. To make it big size we gave it OutlineLevel and font size 25. We also added a border of width 1pt below it and gave spacing of 30pt after it. See the below code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var heading = section.AddParagraph(&quot;Your Credit Card Statement Report has been Generated&quot;);

heading.Format.OutlineLevel = OutlineLevel.Level1;
heading.Format.Font.Size = 25;

// Add line below the heading
heading.Format.Borders.Bottom.Width = 1;
heading.Format.SpaceAfter = &quot;30pt&quot;;
</pre></div>


<p>The above code will generate the following as shown by the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/migradoc-paragraph-border.png" alt="MigraDoc Paragraph Border" title="MigraDoc Paragraph Border" class="img-fluid"></p>
<h2>MigraDoc Table</h2>
<p>Next, we defined a table which will contain 2 columns. The left column will contain the customer image and the right column will contain the customer name, address and other details. The below image shown the details.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/migradoc-table.png" alt="MigraDoc Table" title="MigraDoc Table" class="img-fluid"></p>
<div class="noteBlock">Interesting to read &#8211; <a href="https://www.yogihosting.com/entity-framework-extensions-bulk-operations/">The Entity Framework Extensions: Performance-Focused (Need for Speed) when working with large Datasets</a></div>
<p>The code which does this thing is given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
// Add table.
var table = section.AddTable();

// Add first column.
var columnA = table.AddColumn(Unit.FromCentimeter(6));

// Add second column.
var columnB = table.AddColumn(Unit.FromCentimeter(12));

// Add first row.
var row1 = table.AddRow();

// Add paragraph to first cell of row1.
var cellA1 = row1&#x5B;0];

document.ImagePath = imagePath;
var image = cellA1.AddImage(&quot;woman.jpg&quot;);
image.Width = Unit.FromPoint(150);
image.Height = Unit.FromPoint(150);

// Add paragraph to second cell of row1.
var cellB1 = row1&#x5B;1];
cellB1.AddParagraph(&quot;Name: Mrs. Grace Kelly&quot;);
cellB1.AddParagraph(&quot;Address: House 20, 31 drowning street, London (UK)&quot;);
cellB1.AddParagraph(&quot;Occupation: Doctor&quot;);
cellB1.AddParagraph(&quot;Age: 30&quot;);
cellB1.Format.Font.Size = 25;
cellB1.Format.Font.Color = Colors.Red;

var heading1 = section.AddParagraph(&quot;This month&#039;s transation in your Credit Card !&quot;);
heading1.Format.Font.Size = 20;
heading1.Format.Font.Color = Colors.BurlyWood;

// Add line below the heading
heading1.Format.Borders.Bottom.Width = 1;
heading1.Format.SpaceBefore = &quot;30pt&quot;;
heading1.Format.SpaceAfter = &quot;30pt&quot;;
</pre></div>


<p class="wp-block-paragraph">If we explain the above code, it starts by adding the table to the section. Then adding the 2 columns of width 6cms and 12cms to the table.</p>



<pre class="wp-block-code"><code>var table = section.AddTable();
var columnA = table.AddColumn(Unit.FromCentimeter(6));
var columnB = table.AddColumn(Unit.FromCentimeter(12));</code></pre>



<p>After that we added a row and the first cell to the row. Note that the first cell has index 0 and second one has index 1.</p>



<pre class="wp-block-code"><code>var row1 = table.AddRow();
var cellA1 = row1&#91;0];</code></pre>



<p class="wp-block-paragraph">The first cell will contain the image of the customer so we have to provide the image path i.e. wwwroot/Images. See below code.</p>



<pre class="wp-block-code"><code>document.ImagePath = imagePath;</code></pre>



<p>Next, we add the image of the customer with dimension 150pt * 150pt.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var image = cellA1.AddImage(&quot;woman.jpg&quot;);
image.Width = Unit.FromPoint(150);
image.Height = Unit.FromPoint(150);
</pre></div>


<p>The second cell is also added in the same way where we have shown the customer details. See the below code.</p>



<pre class="wp-block-code"><code>var cellB1 = row1&#91;1];
cellB1.AddParagraph("Name: Mrs. Grace Kelly");
cellB1.AddParagraph("Address: House 20, 31 drowning street, London (UK)");
cellB1.AddParagraph("Occupation: Doctor");
cellB1.AddParagraph("Age: 30");
cellB1.Format.Font.Size = 25;
cellB1.Format.Font.Color = Colors.Red;</code></pre>



<p>After this we added another table which contains 5 columns to show the credit card transaction details of the customer. The below image shows this.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/migradoc-table-example.png" alt="MigraDoc Table Example" title="MigraDoc Table Example" class="img-fluid"></p>
<p>There is nothing new to this code and it is self explanatory.</p>
<p>The next part of the pdf contains the final message to the customer. The code which does this thing is given below.</p>



<pre class="wp-block-code"><code>var custName = section.AddParagraph("Hello Grace,");
custName.Format.SpaceBefore = "30pt";
custName.Format.SpaceAfter = "20pt";
section.AddParagraph("Thank you for being our valuable customer. We hope our letter finds you in the best of health and wealth.\n\nYours Sincerely.\nICICI Bank");</code></pre>



<p>The below image shown this portion of the pdf.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/migradoc-example.png" class="img-fluid" alt="MigraDoc Example" title="MigraDoc Example"></p>
<h2>MigraDoc PDF Rendering</h2>
<p>We have added all the details to the pdf. We now have to save the pdf file. For this we use the <span class="term">PdfDocumentRenderer</span> object to render the pdf. The pdf file will be named as &#8220;SimpleDocument.pdf&#8221; and will be saved on the root of the app.</p>
<div class="note">Conclusion</div>
<p>Well that&#8217;s it we just completed the full pdf generation with MigraDoc. You can create any type of pdf with this library free of charge. Download the source codes from our GitHub repo (link at the top) and start using this library.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-pdf-migradoc/">How to create PDF files in ASP.NET Core with MigraDoc</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-pdf-migradoc/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
