<?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>Mon, 03 Aug 2026 04:33:14 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.3</generator>
	<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>
		<item>
		<title>How to use RabbitMQ with MassTransit for ASP.NET Core Microservices Communication</title>
		<link>https://www.yogihosting.com/aspnet-core-rabbitmq-masstransit/</link>
					<comments>https://www.yogihosting.com/aspnet-core-rabbitmq-masstransit/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 16:06:37 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22863</guid>

					<description><![CDATA[<p>RabbitMQ is a popular message broker used to enable reliable, scalable, and asynchronous communication between different components of an application. In the context of ASP.NET Core, RabbitMQ is commonly integrated to decouple services, improve performance, and handle background processing efficiently. When building modern web applications with ASP.NET Core, especially in microservices architectures, direct communication between [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-rabbitmq-masstransit/">How to use RabbitMQ with MassTransit for ASP.NET Core Microservices Communication</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>RabbitMQ is a popular message broker used to enable reliable, scalable, and asynchronous communication between different components of an application. In the context of ASP.NET Core, RabbitMQ is commonly integrated to decouple services, improve performance, and handle background processing efficiently.</p>
<p>When building modern web applications with ASP.NET Core, especially in microservices architectures, direct communication between services can lead to tight coupling and reduced flexibility. RabbitMQ helps solve this by acting as an intermediary that manages message queues. Instead of services calling each other directly, they send messages to a queue, which are then consumed by other services when they are ready. This approach enhances fault tolerance and allows systems to scale independently.</p>



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



<p>Using RabbitMQ in ASP.NET Core typically involves producing messages (publishers) and consuming them (consumers). For example, an &#8220;Order&#8221; Microservice (Publisher) takes an order from a customer, and publishes a message to RabbitMQ about this order. RabbitMQ sends this Order message to the &#8220;Shipping&#8221; Microservice (Consumer), so that the shipping microservice can ship the order to the customer. This asynchronous workflow ensures that the whole app remains responsive while heavy tasks are handled separately.</p>
<h2>ASP.NET Core Microservices example with RabbitMQ</h2>
<p>In this .NET example we will use RabbitMQ as a message broker for Microservices communication. There will be 2 Microservices build on ASP.NET Core these are:</p>
<ul>
<li>Order Microservice (Publisher)- that will take the customer order and produce the message regarding the order for RabbitMQ. RabbitMQ will send this order message to the Shipping Mircoservice.</li>
<li>Shipping Microservice (Consumer) &#8211; will get the Order message from RabbitMQ regarding the Order message, and will ship the product to the customer.</li>
</ul>
<p>We have explained the whose process in the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-aspnetcore-example.png" alt="RabbitMQ ASP.NET Core Example" title="RabbitMQ ASP.NET Core Example" class="img-fluid"></p>
<div class="starBlock">Download the full source codes of these Microservices from my <a href="https://github.com/yogyogi/RabbitMQ-with-MassTransit-for-ASP.NET-Core-Microservices-Communication" target="_blank">GitHub Repository</a>.</div>
<h3>Installing RabbiMQ</h3>
<p>We will install RabbitMQ through Docker. This is a very fast process. We just run the following command on command prompt or Powershell.</p>



<pre class="wp-block-code"><code>docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4-management</code></pre>



<p>Check the below image which shows this command.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-docker.png" alt="RabbitMQ Docker" title="RabbitMQ Docker" class="img-fluid"></p>
<p>In 1 to 2 minutes RabbitMQ image will be downloaded on our pc and will run from a docker container. Open the RabbitMQ url &#8211; <u>http://localhost:15672/</u> on the browser. Then for both username and password enter &#8220;guest&#8221; and click the login button.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-login.png" alt="RabbitMQ Login" title="RabbitMQ Login" class="img-fluid"></p>
<p>Once login we will see tabs for &#8211; Overview, Connections, Channels, Exchanged, Queues and Streams and Admin. Navigate to any of them shows the specific details.</p>
<div class="note">Channels and Connections</div>
<p>In RabbitMQ, a channel is a lightweight virtual connection that runs on top of a real connection. It’s the main way your application actually interacts with RabbitMQ to send and receive messages.</p>
<p>A connection is a fundamental link established between your application (such as an ASP.NET Core MVC) and the RabbitMQ server.</p>
<p>If you navigate to Channels and Connections they will show nothing since we haven&#8217;t yes interacted with RabbitMQ with our ASP.NET Core MVC app.</p>
<div class="note">Exchanges</div>
<p>In RabbitMQ, an exchange is the component responsible for receiving messages from producers and deciding how to route them to queues. It acts like a smart message router. An exchange receives messages from a producer (publisher), uses rules (called bindings) to determine where messages go and Sends messages to one or more queues. Note that producers never send messages directly to queues—they always send them to an exchange.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-exchanges.png" alt="RabbitMQ Exchanges" title="RabbitMQ Exchanges" class="img-fluid"></p>
<p>Navigate to &#8220;Exchanges&#8221; where you will find few exchanges already present, these are:</p>
<ul>
<li>Default Exchange : “AMQP default” usually refers to the default exchange that is automatically created by the broker when it starts. It’s part of the AMQP standard.</li>
<li>Direct Exchange : Routes messages based on an exact match of the routing key.</li>
<li>Fanout Exchange : Sends messages to all bound queues.</li>
<li>Topic Exchange : Routes messages using pattern matching.</li>
<li>Headers Exchange : Routes based on message headers instead of routing key.</li>	
</ul>
<div class="note">Queues and Streams</div>
<p>Queues and Streams are two different ways of storing and delivering messages. They serve different use cases depending on how you want messages to be processed.</p>
<p>A queue:</p>
<ul>
<li>Stores messages until they are consumed.</li>
<li>Delivers messages to consumers (usually one at a time).</li>
<li>Removes messages once they are acknowledged.</li>
</ul>
<p>Streams are a newer feature designed for high-throughput and event streaming scenarios.</p>
<p>A stream:</p>
<ul>
<li>Stores messages as a continuous log (like an event history).</li>
<li>Messages are not deleted after consumption.</li>
<li>Consumers can read messages multiple times.</li>
</ul>
<p>On navigating to Queues and Streams tab there won&#8217;t be any since we haven&#8217;t created a message. We will do it in just a moment.</p>
<div class="starBlock"><p>jQuery DataTables is a powerful plugin for creating interactive HTML tables with:</p>
<ul>
<li>pagination, searching, sorting, filtering</li>
<li>AJAX loading, and many more features</li>
</ul>
<p>I have integrated it in ASP.NET Core, read my article &#8211; <a href="https://www.yogihosting.com/aspnet-core-jquery-datatables/">jQuery DataTables in ASP.NET Core with Server Side Processing</a></p>
</div>
<h2>ASP.NET Core Publisher Microservice</h2>
<p>Lets start with creating a Publisher Microservice. In visual studio create a new project by selecting the template called ASP.NET Core Web App (Model-View-Controller). We named this project <u>MTTutorialP</u>, you can name it anything. This microservices acts as a Publisher that can interact with other Consumer microservices via RabbitMQ.</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>We first add an <u>Order.cs</u> class to the project which is for the orders made by customers. We gave the namespace <span class="term">MTTutorialC.Models</span> for this class. We will use the same class with the same namespace on the Consumer also. This is because RabbitMQ treats messages based on it’s namespaces. if the received and outgoing message are of different namespace (signatures), RabbitMQ would not recognize the Consumer.</p>



<pre class="wp-block-code"><code>namespace MTTutorialC.Models
{
    public class Order
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Quantity { get; set; }
    }
}</code></pre>



<h3>Installing MassTransit Packages</h3>
<p>MassTransit is a free, open-source &#8220;distributed application framework&#8221; for .NET. At its core, it acts as a service bus—a layer of abstraction that sits on top of message brokers (like RabbitMQ, Azure Service Bus, or Amazon SQS) to make building message-based, loosely coupled applications much easier. Think of it as an &#8220;Object-Relational Mapper&#8221; (ORM) but for messaging. Just as Entity Framework abstracts the complexities of SQL, MassTransit abstracts the complexities of message brokers. So install the following 2 MassTransit packages to the project from NuGet:</p>



<pre class="wp-block-code"><code>Install-Package MassTransit
Install-Package MassTransit.RabbitMQ</code></pre>



<div class="note">Configuring MassTrasit for Publisher</div>
<p>After adding the MassTransit packages, we will have to configure it to work as a Publisher. Navigate to the Program.cs and add the following code that registers MassTransit as a service.</p>



<pre class="wp-block-code"><code>builder.Services.AddMassTransit(x =&gt;
{
    x.UsingRabbitMq();
});</code></pre>



<p>Note that in the above case the MassTransit will use default RabbitMQ username and password which is &#8220;guest&#8221;. If the username and password are different then you can specify them using the below configurations.</p>



<pre class="wp-block-code"><code>builder.Services.AddMassTransit(x =&gt;
{
    x.UsingRabbitMq((context, cfg) =&gt;
    {
        cfg.Host("localhost", "/", h =&gt;
        {
            h.Username("myusername");
            h.Password("mypassword");
        });
    });
});</code></pre>



<h3>Publisher: Send Message to RabbitMQ with MassTransit</h3>
<p>Lets post an Order message to RabbitMQ with MassTransit. Open HomeController.cs file and inject ISendEndpointProvider object to the constructors. This object will be provided by the dependency injection.</p>
<p>We then use the ISendEndpointProvider to get the endpoint address and using it we send the order message to RabbitMQ. Check the below code.</p>



<pre class="wp-block-code"><code>public class HomeController : Controller
{
    private readonly ISendEndpointProvider sendEndpointProvider;

    public HomeController(ISendEndpointProvider sendEndpointProvider)
    {
        this.sendEndpointProvider = sendEndpointProvider;
    }
    
    public async Task&lt;IActionResult&gt; Index()
    {
        var endpoint = await sendEndpointProvider.GetSendEndpoint(new Uri("queue:OrderC"));

        await endpoint.Send(new Order {
            Id = 1,
            Name = "Football",
            Quantity = 10
        });

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



<p>Notice the GetSendEndpoint method needs the RabbitMQ queue name which is given as <span class="term">queue:OrderC</span>. Here &#8220;OrderC&#8221; is the name of the queue. Name of the queue can be anything.</p>
<p>The Order message containing the Order.cs class values of id=1, name=football and quantity=10 is send to RabbitMQ.</p>
<p>Lets test the working by running the project. The Index action of HomeController will execute automatically as it is the default route of the ASP.NET Core MVC project. Next, open RabbitMQ UI on browser and check the Connections and Channels where we will see new entry, this specifies that RabbitMQ has received the message.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-connections.png" alt="RabbitMQ Connections" title="RabbitMQ Connections" class="img-fluid"></p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-channels.png" alt="RabbitMQ Channels" title="RabbitMQ Channels" class="img-fluid"></p>
<p>The most important thing is the addition of a new exchange called &#8220;OrderC&#8221; which we can find on the Exchanges area. Check the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-exchanges.png" alt="RabbitMQ Channels" title="RabbitMQ Channels" class="img-fluid"></p>
<p>Recall we gave the queue name &#8220;OrderC&#8221; in the URI of <span class="term">GetSendEndpoint()</span> method.</p>
<p>Now go to Queues and Streams where we find a new entry. The message is waiting for a consumer to pick them up for processing. See the state of the message showing 0 for Ready, Unacked and Total which means message is waiting for a consumer. Check the below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/undelivered-message-rabbitmq.png" alt="RabbitMQ Undelivered Message" title="RabbitMQ Undelivered Message" class="img-fluid"></p>
<p>Click on the message to see it details, we can see there are no consumers for the message. Check the below image.</p> 
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-noconsumer.png" alt="RabbitMQ NoConsumer" title="RabbitMQ NoConsumer" class="img-fluid"></p>
<p>In RabbitMQ, the terms Ready, Unacked, and Total describe the state of messages in a queue:</p>
<div class="note">Ready</div>
<ul>
<li>Messages that are waiting in the queue</li>
<li>Not yet delivered to any consumer</li>
<li>Available to be consumed immediately</li>
</ul>
<div class="note">Unacked (Unacknowledged)</div>
<ul>
<li>Messages that have been delivered to a consumer</li>
<li>But the consumer has not yet acknowledged (ACKed) them</li>
<li>These are “in progress”</li>
<li>If the consumer crashes or disconnects, RabbitMQ will requeue them</li>
</ul>
<div class="note">Total</div>
<ul>
<li>The sum of Ready + Unacked</li>
<li>Represents all messages currently in the queue</li>
</ul>
<h2>ASP.NET Core Consumer Microservice</h2>
<p>Lets add a Consumer, so right click on the Solution and select add a new project. Select the same old template of ASP.NET Core Web App (Model-View-Controller), and name the project as &#8220;MTTutorialC&#8221;. Name is not important and you can choose your own name.</p>
<p>This .NET Microservice will be responsible for consuming the incoming messages from RabbitMQ. Do you remember the packages we installed earlier? Install the same one in this project too.</p>
<p>To this project add the Order.cs class that has the name namespace like the Order.cs defined in the publisher project. This is the requirement for RabbitMQ.</p>



<pre class="wp-block-code"><code>namespace MTTutorialC.Models
{
    public class Order
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Quantity { get; set; }
    }
}</code></pre>



<div class="note">Configuring MassTrasit for Consumer</div>
<p>Next, we configure the MassTransit in the program class as shown below.</p>



<pre class="wp-block-code"><code>builder.Services.AddMassTransit(x =&gt;
{
    x.AddConsumer&lt;OrderC&gt;();

    x.UsingRabbitMq((context, cfg) =&gt;
    {
        cfg.ConfigureEndpoints(context);
    });
});</code></pre>



<p>In the above code we added the consumer &#8211; <span class="code">x.AddConsumer&lt;OrderC>()</span>. OrderC is the consumer class which will receive the message.</p>
<p>Next, we configured the Endpoints of RabbitMQ for MassTransit using the below code:</p>



<pre class="wp-block-code"><code>x.UsingRabbitMq((context, cfg) =&gt;
{
    cfg.ConfigureEndpoints(context);
});</code></pre>



<h3>Consumer: Receive Message from RabbitMQ with MassTransit</p>
<p>To receive messages from RabbitMQ using MassTransit, we need to define a class that will be the consumer for the message. It has to inherit the IConsumer&#038;ltT> where T is the type of message which is &#8220;Order&#8221; for our case. The code of this class called <span class="term">OrderC.cs</span> is given below.</p>



<pre class="wp-block-code"><code>namespace MTTutorialC.Models
{
    public class OrderC : IConsumer&lt;Order&gt;
    {
        public async Task Consume(ConsumeContext&lt;Order&gt; context)
        {
            var jsonMessage = JsonConvert.SerializeObject(context.Message);
            Console.WriteLine($"OrderCreated message: {jsonMessage}");
        }
    }
}</code></pre>



<p>The message received is serialized by the Json.NET library which we can install by the below command.</p>



<pre class="wp-block-code"><code>Install-Package Newtonsoft.Json</code></pre>



<h2>Testing the Microservices</h2>
<p>Let’s test our Microservices now. We need both the Microservices running in order to send and receive the messages. To enable Multiple Starup Projects, Right click on the solution and set the action of each project to &#8220;Start&#8221;. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/microservice-startup.png" alt="Microservice Startup" title="Microservice Startup" class="img-fluid"></p>
<p>Place a breakpoint on the line &#8220;var jsonMessage&#8221; in the consumer. Now press the Run button on Visual Studio which will start both the projects.</p>
<p>The breakpoint will hit and we can see the message is received by the consumer. Check the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-message.png" alt="RabbitMQ Message" title="RabbitMQ Message" class="img-fluid"></p>
<p>Go to Queues and Streams on the RabbitMQ UI where we will find a new queue is formed. Check the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/rabbitmq-queues-streams.png" alt="RabbitMQ Queues Streams" title="RabbitMQ Queues Streams" class="img-fluid"></p>
<p>Click the Queue to see it&#8217;s details check the consumer binding now present.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/consumer-binding-rabbitmq.png" alt="RabbitMQ Consumer Binding" title="RabbitMQ Consumer Binding" class="img-fluid"></p>
<h3>Publisher sends message Consumer unavailable</h3>
<p>The consumer can be offline due to several reasons like server issues. Even if the consumer is offline, the publisher can still send messages to the RabbitMQ queue. Once the consumer comes back online, it can process any pending messages. That’s essentially the core idea behind message brokering—let’s take a closer look.</p>
<p>Change the startup to run only the Publisher project (and not Consumer). This mimics the scenario where the consumer if offline. If we run the publisher project the message goes to rabbitmq queue.</p>
<p>Now change the setup to run only the Consumer project. Put a breakpoint on the OrderC.cs class Consume method. Run the project in Visual Studio, we will see breakpoint hits telling the message is received from RabbitMQ.</p> 
<div class="note">Conclusion</div>
<p>In this article, we explored message brokers, RabbitMQ, its advantages, and how to integrate it with ASP.NET Core using MassTransit. We also built a small prototype application to demonstrate sending data through a RabbitMQ server. You can find the complete source code for this implementation here.</p>
<p>Feel free to share your questions and suggestions in the comments below. If you found this article helpful or learned something new, consider sharing it with your developer community. Happy coding!</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-rabbitmq-masstransit/">How to use RabbitMQ with MassTransit for ASP.NET Core Microservices Communication</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-rabbitmq-masstransit/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>jQuery DataTables in ASP.NET Core with Server Side Processing</title>
		<link>https://www.yogihosting.com/aspnet-core-jquery-datatables/</link>
					<comments>https://www.yogihosting.com/aspnet-core-jquery-datatables/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Sun, 12 Apr 2026 06:02:07 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22845</guid>

					<description><![CDATA[<p>jQuery DataTables is a jquery plugin that turns a normal HTML table into an interactive table with features like search, sorting, pagination, and searching (filter records). It’s widely used in web development to display large datasets in a clean, user-friendly way. In this tutorial we will use jQuery DataTables in ASP.NET Core with Server Side [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-jquery-datatables/">jQuery DataTables in ASP.NET Core with Server Side Processing</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>jQuery DataTables is a jquery plugin that turns a normal HTML table into an interactive table with features like search, sorting, pagination, and searching (filter records). It’s widely used in web development to display large datasets in a clean, user-friendly way. In this tutorial we will use jQuery DataTables in ASP.NET Core with Server Side Processing. We will fetch data from SQL Server database with Entity Framework Core and then display this data in jQuery Datatables. In DataTables we can also use features like search, sorting, pagination, and filtering.</p>



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



<p>In this tutorial we will be building a simple real-world implementation to help understand jQuery DataTables to it’s fullest. The whole working is given by the below gif image. It will have paginations, to searching, to sorting and deleting.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/working-video.gif" class="img-fluid" alt="jQuery DataTables working Video" title="jQuery DataTables working Video"></p>
<div class="starBlock">The source codes of this tutorial is available to download from my <a href="https://github.com/yogyogi/jQuery-DataTables-in-ASP.NET-Core-with-Server-Side-Processing" target="_blank">GitHub Repository</a>.</div>
<p>Key Features of jQuery DataTables:</p>
<ul>
<li>Pagination – Automatically splits large tables into number based pages.</li>
<li>Search / Filtering – Users can quickly search inside the table.</li>
<li>Column Sorting – Click column headers to sort them in ascending/descending way.</li>
<li>Responsive Tables – Works on mobile and desktop, responsiveness through Bootstrap.</li>
<li>AJAX Data Loading – Load data dynamically from APIs or servers.</li>
<li>Export Options – Export to CSV, Excel, PDF, etc.</li>
<li>Custom Styling – Works with frameworks like Bootstrap.</li>
</ul>
<h2>Client-side Pagination vs Server-side Pagination</h2>
<p>Pagination in a grid means splitting a large set of data into smaller pages so the user only sees a limited number of rows at a time instead of the entire dataset. Suppose a grid contains 1,000 records then instead of showing all 1,000 rows at once, pagination shows something like:</p>
<ul>
<li>Page 1: rows 1–10</li>
<li>Page 2: rows 11–20</li>
<li>Page 3: rows 21–30</li>
<li>… and so on.</li>
</ul>
<p>Advantage of Pagination are:</p>
<ul>
<li>Better performance – Loading fewer records at once makes the page faster.</li>
<li>Better user experience – Easier to read smaller sets of data.</li>
<li>Reduced server load – Especially when data comes from a database.</li>
<li>Cleaner UI – Avoids very long scrolling tables.</li>
</ul>
<p><b>Client-side Processing</b> means all the records are loaded into the browser first, and the pagination (page switching) is handled using jQuery on the client (browser), instead of requesting new data from the server each time.</p>
<p>Server-side Processing means the server sends only a small portion of records (i.e. records for a particular page number) to the browser instead of sending the entire records at once. When the user moves to another page, the browser requests the records for that page from the server.</p>
<p>jQuery DataTables offers both Client and Server Side Processing. Client Side Processing works best when there are less than 1000 records to show on jQuery DataTables. If you have more than 1000 records then it is important to use Server Side Processing where only the records for the particular page are fetched from the server. With Server Side Processing you are drastically improving the load time (as the JQuery Datatable is loading just the records of the particular page and not each and every record), reducing the CPU and bandwidth usage.</p>
<div class="starBlock">Related article &#8211; <a href="https://www.yogihosting.com/jquery-datatables/">Learn jQuery DataTables in 2 minutes</a></div>
<h2>ASP.NET Core Database driven app</h2>
<p>In our ASP.NET Core app, we will read Employee records from the database using Entity Framework Core. Then these records are display in jQuery DataTables. First, add Employee.cs in the Models folder.</p>



<pre class="wp-block-code"><code>public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Gender { get; set; }
    public string Email { get; set; }
    public string Telephone { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Designation { get; set; }
}</code></pre>



<p>We add the following packages to set up Entity Framework Core.</p>



<pre class="wp-block-code"><code>Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Design</code></pre>



<p>Next, we add DbContext file called <span class="term">CompanyContext.cs</span> inside the Models folder.</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>After this add the connection string in the appsettings.json as 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=true;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
}</code></pre>



<p>With everything set, we perform the Migrations. Open up your package manager console and use the following commands.</p>



<pre class="wp-block-code"><code>add-migration Migration1
update-database</code></pre>



<p>With the database ready, we add 1000 dummy records to the database. There are many free tools available to generate dummy data like &#8211; generatedata, mockaroo, etc. We generated 1000 records for Employee table in SQL Insert script and executed the script directly in the database.</p>
<p>Good news! We will be providing 1000 dummy records SQL Insert script for you to use. Check the GitHub repository.</p>
<h3>Create HTML Table for Employee Records</h3>
<p>In order to implement JQuery Datatable, lets define the structure of our HTML Table in a Razor View File. In the Views Folder, edit up the Index.cshtml to include the HTML table as shown below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
@{
    ViewData&#x5B;&quot;Title&quot;] = &quot;Home Page&quot;;
}

&lt;link href=&quot;//cdn.datatables.net/2.3.7/css/dataTables.dataTables.min.css&quot; rel=&quot;stylesheet&quot; /&gt;

&lt;div class=&quot;text-center&quot;&gt;
    &lt;h1 class=&quot;display-4&quot;&gt;Welcome&lt;/h1&gt;
    &lt;p&gt;Learn about &lt;a href=&quot;https://learn.microsoft.com/aspnet/core&quot;&gt;building Web apps with ASP.NET Core&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;div class=&quot;container&quot;&gt;
    &lt;table id=&quot;employeeDataTable&quot; class=&quot;table table-striped table-bordere&quot;&gt;
        &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;ID&lt;/th&gt;
                &lt;th&gt;First Name&lt;/th&gt;
                &lt;th&gt;Last Name&lt;/th&gt;
                &lt;th&gt;Gender&lt;/th&gt;
                &lt;th&gt;Email&lt;/th&gt;
                &lt;th&gt;Telephone&lt;/th&gt;
                &lt;th&gt;DateOfBirth&lt;/th&gt;
                &lt;th&gt;Designation&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
    &lt;/table&gt;
&lt;/div&gt;

@section Scripts
{
    &lt;script src=&quot;//cdn.datatables.net/2.3.7/js/dataTables.min.js&quot;&gt;&lt;/script&gt;

    &lt;script&gt;
        $(document).ready(function () {
            $(&#039;#employeeDataTable&#039;).dataTable({
            
            });
        });
    &lt;/script&gt;
}
</pre></div>


<p>This will render jQuery DataTables as shown by the image below.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/jquery-datatables-empty.png" class="img-fluid" alt="jQuery DataTables Empty" title="jQuery DataTables Empty"></p>
<p>Although, we get the message &#8220;No data available in table&#8221; which is obvious since we have not applied the data to the DataTables till now. This we will do in just a moment.</p>
<p>Let&#8217;s understand the above code.</p>
<ol>
<li>We created an HTML table to display Employee records.</li>
<li>On the top we provide the link to jQuery DataTables css.</li>
<li>On the bottom inside the script tag we provided the like of jQuery DataTables JavaScript file.</li>
<li>Both the CSS and JavaScript will be downloaded from CDN.</li>
<li>We invoke the DataTable() method by using the ID of the employee table which is <u>employeeDataTable</u>. This is how to initialize the datatable.</li>
</ol>
<p>The below line initialize the jQuery DataTables.</p>



<pre class="wp-block-code"><code>&lt;script&gt;
    $(document).ready(function () {
        $('#employeeDataTable').dataTable({
            
        });
    });
&lt;/script&gt;</code></pre>



<h2>How to use jQuery DataTables</h2>
<p>Lets understand how to use DataTables by configuring it. Update the jQuery DataTables as shown below.</p>



<pre class="wp-block-code"><code>&lt;script&gt;
    $(document).ready(function () {
        $('#employeeDataTable').dataTable({
            processing: true,
            serverSide: true,
            "filter": true,
            "ajax": {
                "url": "/api/Employee/GetEmployees",
                "type": "POST",
                "datatype": "json"
            },
            "columns": &#91;
                { "data": "id" },
                { "data": "firstName" },
                { "data": "lastName" },
                { "data": "gender" },
                { "data": "email" },
                { "data": "telephone" },
                { "data": "dateOfBirth"},
                { "data": "designation" }
            ]
        });
    });
&lt;/script&gt;</code></pre>



<p>In the above code we applied parameters to configure our jQuery DataTables. These parameters are:</p>
<ul>
<li><u>processing</u> &#8211; A boolean property that is used to control the visibility of the &#8220;processing&#8221; indicator message.</li>
<li><u>serverSide</u> &#8211; This property enables server-side processing.</li>
<li><u>filter</u> &#8211; enables/disables the search bar.</li>
<li><u>ajax</u> &#8211; used to fetch the data from external sources using JavaScript AJAX. We will create Web API though which this data will be fetched.</li>
<li><u>columns</u> &#8211; columns declared in the DataTables.</li>
</ul>
<p>See the &#8220;columns&#8221; options given as <span class="code">data: columnname</span>. Let understand it deeper. When naming variables, it’s important to follow camelCase conventions. For example, firstName is correct, while FirstName is not. It might seem a bit unusual, but that’s how JavaScript works—so be sure to stick to camelCase when writing your scripts.</p>
<p>Also, keep in mind that the API returns a list of records under the key &#8216;data&#8217;. That’s why we reference fields as data.id, data.firstName, and so on when defining column values.</p>
<p>The ajax url is given as <span class="code">&#8220;url&#8221;: &#8220;/api/Employee/GetEmployees&#8221;</span>. It is the url of the Web API that will return the employee records. Let&#8217;s create the Web API.</p>
<div class="starBlock"><p>RabbitMQ + MassTransit is a very common combination in .NET microservices for asynchronous messaging, event-driven architecture, and distributed systems.</p>
<ul>
<li>RabbitMQ = message broker</li>
<li>MassTransit = abstraction/framework over message brokers for .NET</li>
</ul>
<p>Read – <a href="https://www.yogihosting.com/aspnet-core-rabbitmq-masstransit/">How to use RabbitMQ with MassTransit for ASP.NET Core Microservices Communication</a></p>
</div>
<h3>Web API to return data for jQuery DataTables</h3>
<p>Add a new API Controller to the Controllers folder and name it <span class="term">EmployeeController.cs</span>. We also need to install the package required for data sorting. So run the below install command on NuGet.</p>



<pre class="wp-block-code"><code>Install-Package System.Linq.Dynamic.Core</code></pre>



<p>After installing the package, you can proceed with adding the necessary code to the EmployeeController.cs.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
using jQueryDataTables.Models;
using Microsoft.AspNetCore.Mvc;
using System.Linq.Dynamic.Core;
using System.Text.Json;

namespace jQueryDataTables.Controllers
{
    &#x5B;ApiController]
    &#x5B;Route(&quot;api/&#x5B;controller]&quot;)]
    public class EmployeeController : ControllerBase
    {
        private CompanyContext context;
        public EmployeeController(CompanyContext cc)
        {
            context = cc;
        }

        &#x5B;HttpPost(&quot;GetEmployees&quot;)]
        public IActionResult GetEmployees()
        {
            var draw = Request.Form&#x5B;&quot;draw&quot;].FirstOrDefault();
            var start = Request.Form&#x5B;&quot;start&quot;].FirstOrDefault();
            var length = Request.Form&#x5B;&quot;length&quot;].FirstOrDefault();
            
            var colIndex = Request.Form&#x5B;&quot;order&#x5B;0]&#x5B;column]&quot;];
            string sortColumn = Request.Form&#x5B;&quot;columns&#x5B;&quot; + colIndex + &quot;]&#x5B;data]&quot;].FirstOrDefault();

            var sortColumnDirection = Request.Form&#x5B;&quot;order&#x5B;0]&#x5B;dir]&quot;].FirstOrDefault();
            var searchValue = Request.Form&#x5B;&quot;search&#x5B;value]&quot;].FirstOrDefault();
            int pageSize = length != null ? Convert.ToInt32(length) : 0;
            int skip = start != null ? Convert.ToInt32(start) : 0;
            int recordsTotal = 0;
            var employeeData = (from t in context.Employee select t);

            if (!(string.IsNullOrEmpty(sortColumn) &&amp; string.IsNullOrEmpty(sortColumnDirection)))
            {
                employeeData = employeeData.OrderBy($&quot;{sortColumn} {sortColumnDirection}&quot;);
            }
            if (!string.IsNullOrEmpty(searchValue))
            {
                employeeData = employeeData.Where(m =&gt; m.FirstName.Contains(searchValue)
                                            || m.LastName.Contains(searchValue)
                                            || m.Email.Contains(searchValue));
            }
            recordsTotal = employeeData.Count();
            var data = employeeData.Skip(skip).Take(pageSize).ToList();
            var jsonData = new { draw = draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = data };

            string jsonString = JsonSerializer.Serialize(jsonData);

            return Ok(jsonData);
        }
    }
}
</pre></div>


<p>The work of the APIController is to fetch the employee records from the database in page by page manner. It uses Entity Framework Core to do this task.</p>
<p>First, we get the values of draw, start &#038; length and order[0][column] from Request.Form. These are used for create pagination in DataTables.  We can see page size in the Dropdown of DataTables that says, ‘Showing n entries’ where n being the page size.</p>



<pre class="wp-block-code"><code>var draw = Request.Form&#91;"draw"].FirstOrDefault();
var start = Request.Form&#91;"start"].FirstOrDefault();
var length = Request.Form&#91;"length"].FirstOrDefault();</code></pre>



<p>The 3 variables colIndex, sortColumn, sortColumnDirection get the values of necessary columns for performing sorting in DataTables. These are using Request.Form method to get these values from the html of the page.</p>



<pre class="wp-block-code"><code>var colIndex = Request.Form&#91;"order&#91;0]&#91;column]"];
string sortColumn = Request.Form&#91;"columns&#91;" + colIndex + "]&#91;data]"].FirstOrDefault();
var sortColumnDirection = Request.Form&#91;"order&#91;0]&#91;dir]"].FirstOrDefault();</code></pre>



<p>The searchValue variable contains the value to search in the DataTables.</p>
var searchValue = Request.Form[&#8220;search[value]&#8221;].FirstOrDefault();
<p>Next, we added the code for fetching and showing the records of the current page.</p>



<pre class="wp-block-code"><code>int pageSize = length != null ? Convert.ToInt32(length) : 0;
int skip = start != null ? Convert.ToInt32(start) : 0;
int recordsTotal = 0;</code></pre>



<p class="wp-block-paragraph">With LINQ Skip and Take method we get these records and convert them to json. This json is send to jQuery DataTables where the records are displayed.</p>



<pre class="wp-block-code"><code>recordsTotal = employeeData.Count();
var data = employeeData.Skip(skip).Take(pageSize).ToList();
var jsonData = new { draw = draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = data };

string jsonString = JsonSerializer.Serialize(jsonData);</code></pre>



<p>Also check the codes that performs the sorting and searching (filtering) of records. Note that searching is performed on FirstName, LastName and Email fields.</p>



<pre class="wp-block-code"><code>if (!(string.IsNullOrEmpty(sortColumn) &amp;&amp; string.IsNullOrEmpty(sortColumnDirection)))
{
    employeeData = employeeData.OrderBy($"{sortColumn} {sortColumnDirection}");
}

if (!string.IsNullOrEmpty(searchValue))
{
    employeeData = employeeData.Where(m =&gt; m.FirstName.Contains(searchValue)
                                || m.LastName.Contains(searchValue)
                                || m.Email.Contains(searchValue));
}</code></pre>



<p>Well it&#8217;s time to see the working. Run the app and you can see the records displayed by the jQuery DataTables. You can see the page size dropdown on the top left, search box on the top right. At the bottom there are the page numbers for navigating between the records. Also see that clicking the column names will sort the records in ascending and descending manner.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/jquery-datatables.png" class="img-fluid" alt="jQuery DataTables" title="jQuery DataTables"></p>
<p>In the below image we are showing the search/filtering feature in jQuery DataTables.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/jquery-datatables-searching.png" class="img-fluid" alt="jQuery DataTables Searching" title="jQuery DataTables Searching"></p>
<h3>Delete a row from jQuery DataTables</h3>
<p>Lets understand how to add delete record feature on jQuery DataTables. Once this feature is complete we get a delete button against every row of records, click on a delete button will delete the corresponding row of data. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/04/jquery-datatables-delete-row.png" class="img-fluid" alt="jQuery DataTables Delete Row" title="jQuery DataTables Delete Row"></p>
<p>For this we need to add a new column for the &#8220;Delete&#8221; button that will show against each row of records. We will use <span class="code">columns.render</span> option in the table initialization. This option accepts a function or a built-in renderer to transform the cell&#8217;s underlying data.</p>
<p>See the below code where we gave a delete button against the rows using render option.</p>



<pre class="wp-block-code"><code>"columns": &#91;
    { "data": "id" },
    { "data": "firstName" },
    { "data": "lastName" },
    { "data": "gender" },
    { "data": "email" },
    { "data": "telephone" },
    { "data": "dateOfBirth"},
    { "data": "designation" },
    {
      "render": function (data, type, full, meta) {
        // 'full' contains the data for the entire row
        return '&lt;button class="btn btn-danger" data-id="' + full.id + '"&gt;Delete&lt;/button&gt;'; }
    }
]</code></pre>



<pre class="wp-block-code"><code>&lt;p&gt;Next, we add click event to this button using the below JS. The JS calls a method named "deleteRecord".&lt;/p&gt;</code></pre>



<pre class="wp-block-code"><code>$('#employeeDataTable tbody').on('click', '.btn-danger', function () {
        // Get the record ID from the button's data attribute
        var recordId = $(this).data('id');

        // Get the specific table row (tr) that was clicked
        var row = $(this).closest('tr');

        // Confirm the deletion with the user (optional)
        if (confirm('Are you sure you want to delete this record?')) {
            deleteRecord(recordId, row);
        }
    });
});</code></pre>



<p>We next add the function called &#8220;deleteRecord&#8221;. This function make use of the jQuery AJAX method to call another method of our Web API which will actually be deleting the records from the database. Check the below code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
function deleteRecord(id, row) {
    $.ajax({
        url: &quot;/api/Employee/&quot; + id, 
        type: &quot;DELETE&quot;, 
        dataType: &quot;json&quot;,
        success: function (response) {
            if (response) {
                var dt = new DataTable(&#039;#employeeDataTable&#039;);
                // Remove the row from the DataTables instance
                // row().remove() deletes the data and node from the browser memory
                // draw(false) updates the display without resetting the pagination
                dt.row(row).remove().draw(false);
                alert(&#039;Record deleted successfully!&#039;);
            } 
            else {
                alert(&#039;Error deleting record on the server.&#039;);
            }
        },
            error: function (xhr, status, error) {
            alert(&#039;An error occurred during the AJAX request: &#039; + error);
        }
    });
}
</pre></div>


<p>In the above code we are providing the row id in the url as <span class="code">url: &#8220;/api/Employee/&#8221; + id</span>. Also note the DataTables method &#8211; <span class="code">row().remove()</span> is used to deletes the data and node from the browser memory and <span class="code">draw(false)</span> updates the display without resetting the pagination.</p>
<p>Finally we add the delete method to our Web Api which does the record deletion and returns a bool value of true (in json) once the deletion is successful.</p>



<pre class="wp-block-code"><code>&#91;HttpDelete("{id}")]
public IActionResult Delete(int id)
{
    bool status = false;
    var entityToDelete = context.Employee.Where(e =&gt; e.Id == id).FirstOrDefault();
    if (entityToDelete != null)
    {
        context.Employee.Remove(entityToDelete);
        context.SaveChanges();
        status = true;
    }
    return new JsonResult(status);
}</code></pre>



<p>Let&#8217;s test the deletion of record. We click the delete button against any row which we want to delete. We will get a successful alert message once the deletion is completed.</p>
<div class="note">Conclusion</div>
<p>In this article, we explored everything you need to know about working with jQuery DataTables in ASP.NET Core using server-side processing. We covered the syntax, required files, and overall integration, and built a clean data table featuring paging, sorting, searching, and efficient server-side handling. You can find the complete source code for this implementation in GitHub repository (link given at the top).</p>
<p>I hope this article helped you gain a solid understanding of jQuery DataTables in ASP.NET Core. If you have any feedback or suggestions, feel free to share them in the comments section below. Don’t forget to share this article with your developer community. Thanks, and happy coding!</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-jquery-datatables/">jQuery DataTables in ASP.NET Core with Server Side Processing</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-jquery-datatables/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to use Prometheus and Grafana in ASP.NET Core</title>
		<link>https://www.yogihosting.com/aspnet-core-prometheus-grafana/</link>
					<comments>https://www.yogihosting.com/aspnet-core-prometheus-grafana/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Wed, 01 Apr 2026 14:06:31 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22806</guid>

					<description><![CDATA[<p>Prometheus is like a health monitoring system for software systems. It continuously checks metrics and alerts you if something goes wrong. You can then analyze and process them as needed. We can integrate Prometheus in .NET apps and provide metrics through an HTTP endpoint which is /metrics and Prometheus periodically pulls data from those endpoints. [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-prometheus-grafana/">How to use Prometheus and Grafana in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Prometheus is like a health monitoring system for software systems. It continuously checks metrics and alerts you if something goes wrong. You can then analyze and process them as needed. We can integrate Prometheus in .NET apps and provide metrics through an HTTP endpoint which is <span class="term">/metrics</span> and Prometheus periodically pulls data from those endpoints. You can visualize data in Prometheus itself or use external tools like Grafana. Grafana enables users to query, correlate, and visualize metrics, logs, traces, and other telemetry from many data source including Prometheus. Grafana creates interactive dashboards for these data. In this tutorial we are going to explore both Prometheus and Grafana and learn how to use them in ASP.NET Core.</p>



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



<div class="starBlock">Download the full source codes of this tutorial from our <a href="https://github.com/yogyogi/Prometheus-and-Grafana-in-ASP.NET-Core">GitHub Repository</a>.</div>
<h2>Install and Configure Prometheus</h2>
<p>Download Prometheus for your OS from <a href="https://prometheus.io/download/" target="_blank">https://prometheus.io/download/</a> and extract the contents of the download. I am using Window 11 OS so I have downloaded the windows version and extracted the zip file contents to a folder in my PC. Inside this folder their are 2 important files <u>prometheus.exe</u> which starts prometheus and <u>prometheus.yml</u> which contains the configurations.</p>
<p>Open the <span class="term">prometheus.yml</span> file to find the below lines. It specifies that Prometheus will run from <u>http://localhost:9090</u> url in the browser.</p>



<pre class="wp-block-code"><code>static_configs:
  - targets: &#91;"localhost:9090"]
   # The label name is added as a label `label_name=&lt;label_value&gt;` to any timeseries scraped from this config.
    labels:
      app: "prometheus"</code></pre>



<p>Next, add the following code lines at the end of this file.</p>



<pre class="wp-block-code"><code>- job_name: 'MyASPNETApp'
  scrape_interval: 5s # Poll every 5 seconds
  static_configs:
    - targets: &#91;"localhost:5284"]  ## Enter the HTTP port number of .NET app</code></pre>



<p>In the above lines job_name is given MyASPNETApp which can be any name of your choice. Then for scrape_interval give 5s, which is the number of seconds at which Prometheus server scrapes metrics from the app. And finally for the targets specify the http url of the app which in my case is localhost:5284. Note that you have to enter HTTP url not HTTPS one. You will find the app&#8217;s http url inside <span class="term">launchsettings.json</span> file.</p>
<p>Save this file and now double click on the <span class="term">prometheus.exe</span> file to start Prometheus. A console window will open showing logs of Prometheus, this specifies that Prometheus is now running. Next, open url &#8211; <u>http://localhost:9090</u> on the browser which opens Prometheus, see below screenshot.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/prometheus-ui.png" alt="Prometheus UI" title="Prometheus UI" class="img-fluid"></p>
<p>We are going to integrate Prometheus in our app.</p>
<h2>Integrate Prometheus in ASP.NET Core app</h2>
<p>First, reference the OpenTelemetry packages. Use the NuGet Package Manager or command line to add the following NuGet packages.</p>



<pre class="wp-block-code"><code>dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore --prerelease
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Instrumentation.Http</code></pre>



<p>Next, open Program.cs class and configure OpenTelemetry with the Prometheus provider.</p>



<pre class="wp-block-code"><code>var otel = builder.Services.AddOpenTelemetry();

// Configure OpenTelemetry Resources with the application name
otel.ConfigureResource(resource =&gt; resource
    .AddService(serviceName: builder.Environment.ApplicationName));

otel.WithMetrics(metrics =&gt; metrics
    .AddAspNetCoreInstrumentation()
    .AddMeter("Microsoft.AspNetCore.Hosting")
    .AddMeter("Microsoft.AspNetCore.Server.Kestrel")
    .AddMeter("System.Net.Http")
    .AddMeter("System.Net.NameResolution")
    .AddPrometheusExporter());</code></pre>



<p>Through WithMetrics() method we are adding Metrics for ASP.NET Core. Metrics are numerical measurements about your application’s behavior and performance. They help you monitor how the app is running in production or during testing. Metrics are usually collected continuously and sent to monitoring tools like Prometheus, Grafana, or Azure Monitor.</p>
<p>Metrics provide quantitative data about your application such as:</p>
<ul>
<li>Number of requests</li>
<li>Response time</li>
<li>Error rates</li>
<li>CPU or memory usage</li>
<li>Database query duration</li>
</ul>
<div class="starBlock"><a href="https://www.yogihosting.com/aspnet-core-health-checks/">ASP.NET Core Health Checks</a> are a built-in way to expose the health/status of your application and its dependencies (database, Redis, APIs, disk space, etc.) through HTTP endpoints like /health. I have written a complete tutorial on this topic which you should not ignore.</div>
<p>They help answer questions like:</p>
<ul>
<li>Is the API slow?</li>
<li>How many users are hitting the server?</li>
<li>Are errors increasing?</li>
</ul>
<p>Finally, add the OpenTelemetry Prometheus Scraping Endpoint middleware. This means the app metrics are going to Prometheus where we can see their details.</p>



<pre class="wp-block-code"><code>// Configure the Prometheus scraping endpoint
app.MapPrometheusScrapingEndpoint();</code></pre>



<p>Run the app in visual studio and visit the uri &#8211; &#8220;/metrics&#8221; which in our case is <u>https://localhost:7285/metrics</u>. Here all the metrics are shown, see the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/metrics.png" alt="Metrics" title="Metrics" class="img-fluid"></p>
<p>Next, on Prometheus go to <span class="term">Status > Target health</span> where you will see the app&#8217;s metrics url listed. This means Prometheus is receiving the apps metrics input correctly. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/Prometheus-target-health.png" alt="Prometheus Target Health" title="Prometheus Target Health" class="img-fluid"></p>
<h2>Adding Custom Metrics and export to Prometheus</h2>
<p>Lets add a Custom Metrics to the app. Start by adding a new class called <u>AppCustomMeter.cs</u> whose code is given below. The class uses IMeterFactory to create a meter instance.</p>



<pre class="wp-block-code"><code>public class AppCustomMeter
{
    public static string name = "CustomMeter";

    private readonly Counter&lt;int&gt; productCounter;
    
    public AppCustomMeter(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create(name);
        productCounter = meter.CreateCounter&lt;int&gt;("Product.Sold");
    }
    public void ProductSold(string productName, int quantity)
    {
        productCounter.Add(quantity, new KeyValuePair&lt;string, object?&gt;("Product.Name", productName));
    }
}</code></pre>



<p>Now on Program.cs register this custom metrics as a singleton.</p>



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



<p>Also add it to the OpenTelemetryBuilder with AddMeter method as shown in highlighted code below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [3]; title: ; notranslate">
otel.WithMetrics(metrics =&gt; metrics
    .AddAspNetCoreInstrumentation()
    .AddMeter(AppCustomMeter.name)
    .AddMeter(&quot;Microsoft.AspNetCore.Hosting&quot;)
    .AddMeter(&quot;Microsoft.AspNetCore.Server.Kestrel&quot;)
    .AddMeter(&quot;System.Net.Http&quot;)
    .AddMeter(&quot;System.Net.NameResolution&quot;)
    .AddPrometheusExporter());
</pre></div>


<p>Now we can test the metrics in work. On the HomeController file, provide the custom metrics class object through DI.</p>



<pre class="wp-block-code"><code>private AppCustomMeter appCustomMeter;

public HomeController(AppCustomMeter appCustomMeter)
{
    this.appCustomMeter = appCustomMeter;
}</code></pre>



<p>Then call the ProductSold method of the metrics to add 3 football quantity.</p>



<pre class="wp-block-code"><code>public IActionResult Index()
{
    appCustomMeter.ProductSold("Football", 3);
    
    return View();
}</code></pre>



<div class="starBlock">Logging in applications means recording important events, messages, errors, and runtime information while the app is running. It is important for Debugging issues, 
Monitoring production systems, Auditing activity, Tracking errors, Performance analysis and Security investigations. You should read my tutorial on <a href="https://www.yogihosting.com/aspnet-core-logging/" target="_blank">ASP.NET Core Logging</a> to understand this topic in details.</div>
<p>Rerun the app, and check the metrics on Prometheus. Click the 3 dots given on the search text box. This will open a menu, select &#8220;Explore metrics&#8221;.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/prometheus-explore-metrics.png" alt="Prometheus Explore Metrics" title="Prometheus Explore Metrics" class="img-fluid"></p>
<p>A Dialog opens where you will see &#8220;Product_Sold_total&#8221;, click the lens icon to explore it.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/prometheus-custom-metrics.png" alt="Prometheus Custom Metrics" title="Prometheus Custom Metrics" class="img-fluid"></p>
<p>The &#8220;Product_Sold_total&#8221; metrics details opens up. Here click the Insert button which will add this metrics to the search text box of the previous dialog.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/prometheus-metrics-details.png" alt="Prometheus Metrics Details" title="Prometheus Metrics Details" class="img-fluid"></p>
<p>In this page, select the Graph and click the Execute button to see the graph. You can see the graph of the football product sold over time. Check the below image where we have explained this thing.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/prometheus-graph.png" alt="Prometheus Graph" title="Prometheus Graph" class="img-fluid"></p>
<p>There are large number of metrics to explore. Example put http_ in the search text box to see the available metrics.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/prometheus-http-metrics.png" alt="Prometheus HTTP Metrics" title="Prometheus HTTP Metrics" class="img-fluid"></p>
<p>In the same way put kestrel in the search text box to see the kestrel related metrics.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/prometheus-kestrel-metrics.png" alt="Prometheus Kestrel Metrics" title="Prometheus Kestrel Metrics" class="img-fluid"></p>
<h2>Integrate Grafana in ASP.NET Core app</h2>
<p>Download Grafana from <a href="https://grafana.com/oss/grafana/" target="_blank">https://grafana.com/oss/grafana/</a> and install it to your pc. On windows you get an installer to install Grafana. After installation open the grafana url &#8211; <u>http://localhost:3000</u>. You&#8217;ll need to log in; the default username and password are both &#8220;admin&#8221;.</p>
<div class="starBlock">Jaeger is an tracing system used to monitor and troubleshoot microservices-based applications like finding out &#8211; Why is a request slow?, Which service failed?, Where is latency occurring?, How do requests flow between services. If you want to <a href="https://www.yogihosting.com/aspnet-core-opentelemetry-jaeger/">Integrate Jaeger in ASP.NET Core then kindly read my article</a>.</div>
<p>Now, go to &#8216;Add new connection&#8217; and search Prometheus for Data Sources then click Prometheus.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/Grafana.png" alt="Grafana" title="Grafana" class="img-fluid"></p>
<p>On the prometheus page click the &#8220;Add new data source&#8221; button. This will add prometheus to grafana.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/Grafana-add-prometheus.png" alt="Grafana add Prometheus" title="Grafana add Prometheus" class="img-fluid"></p>
<p>Next, you will be taken to the prometheus configurations page. Here add prometheus server url which is <u>http://localhost:9090</u>. Then click &#8220;Save &#038; test&#8221; button. This will show successful message along with a link &#8220;building a dashboard&#8221;. Click this link to start building the dashboard. Check the below image screenshot.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/grafana-prometheus-configurations.png" alt="Grafana Prometheus Configurations" title="Grafana Prometheus Configurations" class="img-fluid"></p>
<p>On the next page, click &#8220;Add visualization&#8221; button.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/grafana-visualization.png" alt="Grafana Visualization" title="Grafana Visualization" class="img-fluid"></p>
<p>Next, click on &#8220;prometheus&#8221; for data source.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/grafana-prometheus-data-source.png" alt="Grafana Prometheus Data Source" title="Grafana Prometheus Data Source" class="img-fluid"></p>
<p>This will take you to Grafana Dashboard where you can select the metrics &#8211; &#8220;&#8221;Product_Sold_total&#8221; and click the &#8220;Run queries&#8221; button to see the graph of this metrics. Check the below image where we have shown this.</p> 
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/Grafana-custom-metrics.png" alt="Grafana Custom Metrics" title="Grafana Custom Metrics" class="img-fluid"></p>
<p>We have successfully integrated Grafana in ASP.NET Core app. We can design sophisticated dashboards that will track any number of metrics. Each metric in .NET can have dimensions, which are key-value pairs that can be used to partition the data.</p>
<div class="note">Conclusion</div>
<p>In this tutorial we learned how to integrate Prometheus and Grafana in ASP.NET Core. We also learned how to create custom metrics and view it&#8217;s details in prometheus. At the end we also learned to create dashboards in Grafana where metrics are viewed in graphs. We hope you liked this tutorial, if any question ask them through the comments section below.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-prometheus-grafana/">How to use Prometheus and Grafana 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-prometheus-grafana/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The Entity Framework Extensions: Performance-Focused (Need for Speed) when working with large Datasets</title>
		<link>https://www.yogihosting.com/entity-framework-extensions-bulk-operations/</link>
					<comments>https://www.yogihosting.com/entity-framework-extensions-bulk-operations/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Tue, 24 Mar 2026 19:48:51 +0000</pubDate>
				<category><![CDATA[EF Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22701</guid>

					<description><![CDATA[<p>Working with large datasets over the standard Entity Framework Core SaveChanges() method can quickly become a performance bottleneck. The reason is due to the SaveChanges() method&#8217;s &#8220;one-by-one&#8221; processing nature and heavy change-tracking overhead. Entity Framework Extensions library eliminates these constraints through some of the popular features on bulk operations. These are inserts, updates, deletes, and [&#8230;]</p>
<p>The post <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> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Working with large datasets over the standard Entity Framework Core SaveChanges() method can quickly become a performance bottleneck. The reason is due to the SaveChanges() method&#8217;s &#8220;one-by-one&#8221; processing nature and heavy change-tracking overhead. Entity Framework Extensions library eliminates these constraints through some of the popular features on bulk operations. These are inserts, updates, deletes, and merges (upserts) on thousands or millions of records in a fraction of the time.</p>
<p>Whether you are handling batch data imports, real-time telemetry, or large-scale synchronization, Entity Framework Extensions library bridges the gap between the productivity of an ORM and the raw speed of specialized data loading tools. Thus offers a &#8220;need for speed&#8221; approach, that can reduce execution times by up to 95%, all while maintaining seamless integration with your existing DbContext and entity configurations.</p>
<p>In this tutorial we will be implementing <a href="https://entityframework-extensions.net/" target="_blank">Entity Framework Extensions</a> library features in our ASP.NET Core app to create popular features on bulk operations.</p>
<div class="starBlock">All these codes are available to download from my <a href="https://github.com/yogyogi/Bulk-Operations-Entity-Framework-Extensions" target="_blank">GitHub repository</a>. You can use these codes freely in your projects.</div>



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



<h2>Integration of Entity Framework Extensions</h2>
<p>In the ASP.NET Core app, open NuGet package manager and install the package <span class="term">Z.EntityFramework.Extensions.EFCore</span>. Since Entity Framework Extensions works with EF Core therefore the app should also have <span class="term">Microsoft.EntityFrameworkCore.SqlServer</span> package installed.</p>
<p>Below I have shown the screenshot of this library from NuGet Package Manager.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/Z.EntityFramework.Extensions.EFCore.png" alt="Z.EntityFramework.Extensions.EFCore" title="Z.EntityFramework.Extensions.EFCore" class="img-fluid"></p>
<h2>Bulk Inserting a large CSV file on SQL Server Database with Entity Framework Extensions</h2>
<p>Lets build the bulk Inserting of records to a SQL Server Database with Entity Framework Extensions library.  I will use a CSV file, containing 50,000 + records of  the World Bank economic survey, for this bulk inserting work. You can find this CSV file on the GitHub repository along with the app.</p>
<p>Note that for working with CSV files, I will be using CsvHelper library, that can be installed from NuGet.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/csvhelper.png" alt="CsvHelper" title="CsvHelper" class="img-fluid"></p>
<p>On the ASP.NET Core app, add the entity class called Survey.cs. This class maps the survey records on the CSV file.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class Survey
{
    public string Year { get; set; }
    public string Industry_aggregation_NZSIOC { get; set; }
    public string Industry_code_NZSIOC { get; set; }
    public string Industry_name_NZSIOC { get; set; }
    public string Units { get; set; }
    public string Variable_code { get; set; }
    public string Value { get; set; }
    public string Variable_name { get; set; }
    public string Variable_category { get; set; }
    public string Industry_code_ANZSIC06 { get; set; }
}
</pre></div>


<p>Next, I add the razor view file called <span class="term">ImportCsv.cshtml</span> file which contains a file upload control for uploading the csv file.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
@{
    ViewData&#x5B;&quot;Title&quot;] = &quot;Upload CSV&quot;;
}

&lt;h1 class=&quot;bg-info text-white&quot;&gt;Upload CSV&lt;/h1&gt;

&lt;h2&gt;@ViewData&#x5B;&quot;Message&quot;]&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 for=&quot;Poster&quot;&gt;&lt;/label&gt;
        &lt;input type=&quot;file&quot; id=&quot;csvfile&quot; name=&quot;csvfile&quot; class=&quot;form-control&quot; /&gt;
    &lt;/div&gt;
    &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Create&lt;/button&gt;
&lt;/form&gt;
</pre></div>


<p>The Controller code which performs the bulk data inserts in the database is given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class AdminController : Controller
{
    private BulkContext context;
    private IWebHostEnvironment hostingEnvironment;
    public AdminController(BulkContext c, IWebHostEnvironment environment)
    {
        context = c;
        hostingEnvironment = environment;
    }

    public IActionResult ImportCsv()
    {
        return View();
    }

    &#x5B;HttpPost]
    &#x5B;ActionName(&quot;ImportCsv&quot;)]
    public async Task&lt;IActionResult&gt; ImportCsv_Post(IFormFile csvfile)
    {
        // By CsvReader Package
        string path = Path.Combine(hostingEnvironment.WebRootPath, &quot;CSV/&quot; + csvfile.FileName);
        using (var stream = new FileStream(path, FileMode.Create))
        {
            await csvfile.CopyToAsync(stream);
        }

        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            PrepareHeaderForMatch = args =&gt; args.Header.ToLower(),
            Delimiter = &quot;,&quot;,
            MissingFieldFound = null,
            BadDataFound=null
        };
        using (var reader = new StreamReader(path))
        {
            using (var csv = new CsvReader(reader, config))
            {
                var records = csv.GetRecords&lt;Survey&gt;();
                context.BulkInsert(records, options =&gt; options.InsertKeepIdentity = true);

            }
        }
        ViewData&#x5B;&quot;Message&quot;] = &quot;Import Successful&quot;;
        return View();
    }

}
</pre></div>


<p>Explanation – Here CsvReader library is used to read the csv file which the user uploads. After preforming some initial configurations, the csv records are read in a Survey class object by the below code:</p>



<pre class="wp-block-code"><code>var records = csv.GetRecords&lt;Survey&gt;();</code></pre>



<p>Next, all the records are bulk insert with Entity Framework Extensions. Just a single line of code to do this job.</p>



<pre class="wp-block-code"><code>context.BulkInsert(records, options =&gt; options.InsertKeepIdentity = true);</code></pre>



<p>I have used Stopwatch class to measure the time it takes to complete this operation. Note that there are massive 50,000 + records to be inserted. Entity Framework Extensions completed this in 2183 milliseconds. This is indeed quite impressive. Check the below screenshot.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/entity-framework-core-extensions-speed-test.png" alt="Bulk Insert" title="Bulk Insert" class="img-fluid"></p>
<p>Need More Speed ? Use BulkInsertOptimized method. It is 25% more fast. See below code line.</p>



<pre class="wp-block-code"><code>context.BulkInsertOptimized(records, options =&gt; options.InsertKeepIdentity = true);</code></pre>



<p>The Key difference between them are:</p>
<ul>
<li>BulkInsert: AutoMapOutputDirection = true by default. It returns values like identity keys but can generate slightly less optimized SQL.</li>
<li>BulkInsertOptimized: AutoMapOutputDirection = false by default. It skips return values for maximum speed, unless you explicitly ask for them.</li>
</ul>
<p>By the way this whole code is there on my GitHub repository which you can copy and use it freely.</p>
<h2>Reading CSV files by old OleDbConnection manner</h2>
<p>We can also read CSV files by old OleDbConnection class without using CsvHelper. The below code does this work.</p>
<p>Note that you have to install the package <span class="term">System.Data.OleDb</span> from NuGet.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpPost]
public async Task&lt;IActionResult&gt; ImportCsv_Old(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;); // The Data Source is the folder, not the file
    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;Survey&gt; records = dataTable.AsEnumerable().Select(row =&gt; new Survey
            {
                Year = row.Field&lt;string&gt;(&quot;year&quot;),// Use .Field&lt;T&gt;() for type safety and null handling
                Industry_aggregation_NZSIOC = row.Field&lt;string&gt;(&quot;vote_average&quot;),
                Industry_code_NZSIOC = row.Field&lt;string&gt;(&quot;industry_code_NZSIOC&quot;),
                // other fiels add heere
            }).ToList();

            context.BulkInsert(records, options =&gt; options.InsertKeepIdentity = true);
        }
    }

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


<h2>Copying large DB data from one Table to another Table with Entity Framework Extensions</h2>
<p>Entity Framework Extensions high speed is useful in making backups of the database tables. This can come handy in situations like copying a large DB table to another table.</p> 
<p>Here I have a massive database containing 200k + movies records in my dabase table called BulkMovies. I will copy all its records to a new table called BulkMoviesCopy.</p> 
<p>To do this I will have to add 2 classes for the movie object as shown below. Since we are making a copy therefore these classes fields are all same.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class BulkMovies
{
    public int? Id { get; set; }
    public string Title { get; set; }
    public double? Vote_Average { get; set; }
    public int? Vote_Count { get; set; }
    public string Status { get; set; }
    public DateTime? Release_Date { get; set; }
    public long? Revenue { get; set; }
    public int? Runtime { get; set; }
    public string Adult { get; set; }
    public int? Budget { get; set; }
    public string Homepage { get; set; }
    public string Overview { get; set; }
    public string Tagline { get; set; }
    public string Genres { get; set; }
    public string Production_Companies { get; set; }
    public string Production_Countries { get; set; }
    public string Spoken_Languages { get; set; }
    public string Keywords { get; set; }
}

public class BulkMoviesCopy
{
    public int? Id { get; set; }
    public string Title { get; set; }
    public double? Vote_Average { get; set; }
    public int? Vote_Count { get; set; }
    public string Status { get; set; }
    public DateTime? Release_Date { get; set; }
    public long? Revenue { get; set; }
    public int? Runtime { get; set; }
    public string Adult { get; set; }
    public int? Budget { get; set; }
    public string Homepage { get; set; }
    public string Overview { get; set; }
    public string Tagline { get; set; }
    public string Genres { get; set; }
    public string Production_Companies { get; set; }
    public string Production_Countries { get; set; }
    public string Spoken_Languages { get; set; }
    public string Keywords { get; set; }
} 
</pre></div>


<p>Next, on a post action method, which is shown below, I have used BulkInsert method to perform the copying task.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpPost]
&#x5B;ActionName(&quot;BulkCopy&quot;)]
public IActionResult BulkCopy_Post()
{
    List&lt;BulkMovies&gt; bm = context.BulkMovies.AsQueryable().ToList();

    List&lt;BulkMoviesCopy&gt; bmc = bm.Select(t =&gt; new BulkMoviesCopy
    {
        Id = t.Id,
        Title = t.Title,
        Vote_Average = t.Vote_Average,
        Vote_Count = t.Vote_Count,
        Status = t.Status,
        Release_Date = t.Release_Date,
        Revenue = t.Revenue,
        Runtime = t.Runtime,
        Adult = t.Adult,
        Budget = t.Budget,
        Homepage = t.Homepage,
        Overview = t.Overview,
        Tagline = t.Tagline,
        Genres = t.Genres,
        Production_Companies = t.Production_Companies,
        Spoken_Languages = t.Spoken_Languages,
        Keywords = t.Keywords
    }).ToList();

    var clock = new Stopwatch();
    clock.Start();

    context.BulkInsert(bmc, options =&gt; options.InsertKeepIdentity = true);

    clock.Stop();
    var time = clock.ElapsedMilliseconds;

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


<p>It should be noted the BulkMoviesCopy object is mapped to the movies data contained by BulkMovies object called &#8220;bm&#8221; using the LINQ Select method as shown below.</p>



<pre class="wp-block-code"><code>List&lt;BulkMovies&gt; bm = context.BulkMovies.AsQueryable().ToList();
List&lt;BulkMoviesCopy&gt; bmc = bm.Select(t =&gt; new BulkMoviesCopy
{
    Id = t.Id,
    Title = t.Title,
    // include the fields that need to be copied
}).ToList();</code></pre>



<p>Then with the BulkInsert method the full data is copied to the BulkMoviesCopy table by the below code line.</p>



<pre class="wp-block-code"><code>context.BulkInsert(bmc, options =&gt; options.InsertKeepIdentity = true);</code></pre>



<p>Below is the result. It took just 9156 milliseconds to perform this job. Considering the records are massive and this little time to complete the job is indeed impressive.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/BulkInsert.png" alt="Bulk Insert" title="Bulk Insert" class="img-fluid"></p>
<h2>Bulk Updates and Deletion</h2>
<p>In Entity Framework Core we have Add and Update method to perform records Inserts and Updates. In Entity Framework Extensions there is a <a href="https://entityframework-extensions.net/bulk-merge" target="_blank">BulkMerge</a> method which does both the work of records addition and records modification.</p> 
<p>It matches the primary key or the composite key of the entity and then automatically decides which record needs to be inserted and which needs to be updated. This decision is made based on:</p>
<ul>
<li>If the DB table already has the row matching the record key (primary or composite) then this row is updated.
<li>If the DB table does not have any row matching the record&#8217;s key then this row is inserted to the table.</li>
</ul>
<p>This certainly reduces the number of code lines if we have to perform multiple tasks like Inserts and Updates on the same method.</p>
<p>Now we are going to implement BulkMerge method in order to perform bulk update of the records.</p>
<p>Add Employee.cs class to the app which is given below:</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 string Address { get; set; }
    public int Salary { get; set; }
}</code></pre>



<p>Next, add an action method of HTTP POST type since we will be calling the codes on the button click. This action method is given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpPost]
public async Task&lt;IActionResult&gt; BulkMerge_Post()
{
    List&lt;Employee&gt; emp = context.Employee.AsQueryable().ToList();

    List&lt;Employee&gt; updateEmployee = emp.Select(t =&gt; new Employee
    {
        Id = t.Id,
        Name = t.Name,
        Designation = t.Designation,
        Address = t.Address,
        Salary = t.Salary + 1000,
    }).ToList();

    context.BulkMerge(updateEmployee);

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


<p>The code above is increasing the salary of every employee by $1000. It is adding the 1000 to the current salary by – Salary = t.Salary + 1000  and then calling the BulkMerge method to perform this update.</p> 
<p>In the same way we can perform the bulk inserts by modified code which is given below. This code inserts the Employees since their Id field is not provided.</p>



<pre class="wp-block-code"><code>List&lt;Employee&gt; insertEmployee = new List&lt;Employee&gt; {
    new Employee{Name="N1", Designation="D1", Address="A1", Salary=1000},
    new Employee{Name="N2", Designation="D2", Address="A2", Salary=2000},
    new Employee{Name="N3", Designation="D3", Address="A3", Salary=3000}
};

context.BulkMerge(insertEmployee);</code></pre>



<p>Mostly in real work scenario you will come acrosss a situation where an excel / CSV file containing the updated data is provided to you. And you as a developer will have to update the database table with this new data on the file. In this situation make sure the key value (which here is  Id) given on the excel / csv should match for the records which have to be updated while the key should be empty for the records that needs to be inserted.</p>
<p>See the below code which does exactly this thing.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
&#x5B;HttpPost]
public async Task&lt;IActionResult&gt; BulkMerge_Post()
{
    // read the excel/csv file code omitted for brevity

    List&lt;BulkMoviesCopy&gt; bmc = dataTable.AsEnumerable().Select(row =&gt; new    BulkMoviesCopy
{
    Id = row.Field&lt;int&gt;(&quot;id&quot;), // Use .Field&lt;T&gt;() for type safety and null handling
    Name = row.Field&lt;string&gt;(&quot;name&quot;),
    Designation = row.Field&lt;string&gt;(&quot;Designation&quot;),
    Address = row.Field&lt;string&gt;(&quot;Address&quot;),
    Salary = row.Field&lt;int&gt;(&quot;Salary&quot;),
}).ToList();

    context.BulkMerge(updateEmployee);

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


<p>The excel file can contain tens of thousands of records and Entity Frameowork Extensions will complete this in a matter of seconds.</p>
<p>The bulk delete feature is provided through BulkDelete method which deletes large number of records in a fraction of seconds. Here also you can read the records that needs to be deleted from an excel or csv file and call this method as shown below.</p>



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



<p>The benefits of BulkDelete method:</p> 
<ol>
<li>Many ways to delete: custom keys, delete related entities and apply any conditions.</li>
<li>Super fast.</li>
<li>No need to load entities. No change tracking.</li>
</ol>
<div class="note">Conclusion</div>
<p>To wrap things up, Entity Framework Extensions serve as the &#8220;turbocharger&#8221; for the standard EF Core engine. While Microsoft has made great strides in optimizing EF Core, developers often hit a performance wall when dealing with large datasets or complex batch operations.</p>
<p>Here is a summary of why they remain a staple in the .NET ecosystem:</p>
<p>The Performance Edge:</p>
<p>The core value proposition is speed. By bypassing the traditional &#8220;one-at-a-row&#8221; processing model and utilizing efficient database-level commands, extensions can turn operations that take minutes into tasks that take seconds.</p>
<p>Key Takeaways:</p>
<p>Scalability: They are essential for enterprise-level applications where data growth is inevitable.</p>
<p>Developer Productivity: You get to keep the clean, LINQ-based syntax you love while gaining the performance of raw SQL.</p>
<p>Resource Efficiency: By reducing the number of round-trips to the database, you lower CPU and memory overhead on both the application server and the database.</p>
<p>Final Verdict:</p>
<p>Entity Framework Extensions are not a replacement for EF Core, but rather a necessary evolution for performance-critical applications. If your project involves data migrations, nightly syncs, or massive user-generated content, the investment in this library usually pays for itself in saved execution time and reduced infrastructure costs.</p>
<p>The post <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> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/entity-framework-extensions-bulk-operations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
