<?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>Fri, 01 May 2026 04:12:18 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.5</generator>
	<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>



<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>
<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>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>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>
<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>
<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>
<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>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>` 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 => resource
    .AddService(serviceName: builder.Environment.ApplicationName));

otel.WithMetrics(metrics => 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>
<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> productCounter;
    
    public AppCustomMeter(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create(name);
        productCounter = meter.CreateCounter&lt;int>("Product.Sold");
    }
    public void ProductSold(string productName, int quantity)
    {
        productCounter.Add(quantity, new KeyValuePair&lt;string, object?>("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>();</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>



<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>
<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>
		<item>
		<title>How to integrate OpenTelemetry with Jaeger for Distributed Tracing in ASP.NET Core</title>
		<link>https://www.yogihosting.com/aspnet-core-opentelemetry-jaeger/</link>
					<comments>https://www.yogihosting.com/aspnet-core-opentelemetry-jaeger/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Tue, 10 Mar 2026 10:54:09 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22733</guid>

					<description><![CDATA[<p>Distributed tracing is a digital &#8220;breadbox trail&#8221; that allows engineers to follow a single request as it hops across various servers, databases, and microservices. It transforms a chaotic web of data into a clear timeline, making it easy to pinpoint exactly where a system is breaking or slowing down. Distributed tracing isolates a single request’s [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-opentelemetry-jaeger/">How to integrate OpenTelemetry with Jaeger for Distributed Tracing in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Distributed tracing is a digital &#8220;breadbox trail&#8221; that allows engineers to follow a single request as it hops across various servers, databases, and microservices. It transforms a chaotic web of data into a clear timeline, making it easy to pinpoint exactly where a system is breaking or slowing down. Distributed tracing isolates a single request’s journey, stitching together every task performed across different services while filtering out the noise of thousands of other simultaneous users. It effectively &#8220;colors&#8221; one specific thread of execution so you can follow it from start to finish without getting lost in the crowd.</p>



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



<p>OpenTelemetry (often called OTel) is the global industry standard for how software applications generate and send data about their health and performance. It is an open source observability framework for cloud native software which provides a single set of APIs, libraries, agents, and collector services to capture distributed traces and metrics from your application.</p>
<p><b>What is Trace and Activity?</b> Each time a new request is received by an app, it can be associated with a trace. In .NET, a trace are represented by instances of System.Diagnostics.Activity and it can be spanning across many distinct processes. The first Activity is the root of the trace tree and it tracks the overall duration and success/failure. Child activities can be created to subdivide the work into different steps that can be tracked individually.</b> 
<h2>What is Jaeger</h2>
<p>Jaeger is an observability platforms to track requests going through a complex web of services. Jaeger acts as the connective tissue, mapping every hop and uncovering hidden delays or failures. By bridging the gaps between isolated components, Jaeger transforms fragmented data into a clear roadmap for troubleshooting, allowing engineers to pinpoint bottlenecks and boost reliability. It is a fully open-source, cloud-native powerhouse built to scale alongside the most demanding architectures.</p>
<p>Jaeger does the following things:</p>
<ul>
<li>Monitor distributed workflows</li>
<li>Find &#038; fix performance bottlenecks</li>
<li>Track down root causes</li>
<li>Analyze service dependencies</li>
</ul>
<p>The below figure explains the Jaeger architecture where it receives the data from traced applications and write it directly to storage. The Jaeger UI is a web-based interface for visualizing and analyzing distributed tracing data, playing a critical role in microservices monitoring. It enables developers to search for specific traces, map request flows, analyze latency bottlenecks, compare trace data, and troubleshoot service errors.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-architecture.png" alt="Jaeger Architecture" title="Jaeger Architecture" class="img-fluid"></p>
<p>First <a href="https://www.jaegertracing.io/download/" target="_blank">download Jaeger</a> from the official website. I am using windows so I will proceed with the windows version. The download file is a compressed file which you need to extract to a folder. You will get jaeger.exe file, next navigate to this folder from command prompt and run the command &#8211; <code>start jaeger</code>. See the below image where I am starting jaeger from command prompt.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/start-jaeger.png" alt="Start Jaeger" title="Start Jaeger" class="img-fluid"></p>
<p>A new command prompt window will open which will show Jaeger logs &#8211; version, ports and configurations. This means Jaeger is in the running state.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-console.png" alt="Jaeger Console" title="Jaeger Console" class="img-fluid"></p>
<p>Open the url &#8211; <u>http://localhost:16686</u> on your browser which will open the Jaeger UI.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-ui.png" alt="Jaeger Console" title="Jaeger Console" class="img-fluid"></p>
<h2>Integrate Jaeger in ASP.NET Core</h2>
<p>First I need to add OpenTelemetry libraries and SDKs to add instrumentation into my ASP.NET Core app. These instrumentations automatically capture the traces, metrics, and logs we are interested in. These libraries need to be installed from NuGet:</p>



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



<p>It&#8217;s time to configure some services in the program class.</p>
<div class="starBlock">The source codes of this tutorial is available to download from my <a href="https://github.com/yogyogi/Integrate-Jaeger-in-ASP.NET-Core" target="_blank">GitHub Repository</a>. You should also need to have SQL database up by running the Migration commands. Make sure Jaeger is also running. Redis Cache and RabbitMQ should be running from docker containers. Note that Redis and RabbitMQ part will come when I will integrate them during the latter half of this tutorial.</div>
<p>Update the code of Program.cs file as given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
var tracingOtlpEndpoint = builder.Configuration&#x5B;&quot;OTLP_ENDPOINT_URL&quot;];
var otel = builder.Services.AddOpenTelemetry();

otel.ConfigureResource(resource =&gt; resource.AddService(serviceName: builder.Environment.ApplicationName));

otel.WithTracing(tracing =&gt;
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =&gt;
        {
            otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
        });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
</pre></div>


<p>In the above code:</p>
<ul>
<li><b>AddOpenTelemetry</b> &#8211; adds OpenTelemetry SDK services</li>
<li><b>AddAspNetCoreInstrumentation</b> &#8211; adds ASP.NET Core instrumentation</li>
<li><b>AddHttpClientInstrumentation</b> &#8211; adds HttpClient instrumentation for outgoing requests</li>
</ul>
<p>I am reading the url of Jaeger endpoint from the appsettings.json from the code line:</p>



<pre class="wp-block-code"><code>var tracingOtlpEndpoint = builder.Configuration\&#91;"OTLP\_ENDPOINT\_URL"];</code></pre>



<p>Then I am adding OpenTelemetry (OTLP) exporter to the trace provider which is Jaeger. The below code line does this work.</p>



<pre class="wp-block-code"><code>tracing.AddOtlpExporter(otlpOptions =>
{
    otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
});</code></pre>



<p>I will have to add the &#8220;OTLP_ENDPOINT_URL&#8221; in the appsettings.json as shown below.</p>



<pre class="wp-block-code"><code>"OTLP_ENDPOINT_URL": "http://localhost:4317/"</code></pre>



<p>The url &#8211; http://localhost:4317/ is where Jaeger is listening to the traffic. This is given on the Jaeger console (which opened when I ran the start jaeger start command). Find the line where Jaeger is listening for OTLP traffic via gRPC to contain this url. The below given line is the one which contains this &#8211; &#8220;endpoint&#8221;: &#8220;127.0.0.1:4317&#8221;.</p>



<pre class="wp-block-code"><code>2026-03-07T11:42:39.708+0530    info    otlpreceiver@v0.145.0/otlp.go:120       Starting GRPC server    {"resource": {"service.instance.id": "171c6e00-b515-48e6-bf89-49c86e4bd3ce", "service.name": "jaeger", "service.version": "v2.15.0"}, "otelcol.component.id": "otlp", "otelcol.component.kind": "receiver", "endpoint": "127.0.0.1:4317"}</code></pre>



<p>Lets run the app and check whether Jaeger is capturing the Traces or not. I will have to refresh the Jaeger UI tab.</p>
<p>Jaeger has captured my Apps traces. My apps name is &#8220;JaegerTutorial&#8221; and it is visible on the dropdown list, the below image shows it.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-traces.png" alt="Jaeger Traces" title="Jaeger Traces" class="img-fluid"></p>
<p>I click on the <u>Find Traces</u> button to see all the traces. Next I select the trace called &#8211; <span class="term">JaegerTutorial: GET {controller=Home}/{action=Index}/{id?} 4241af5</span>. This shown the HTTP GET traces captured when the Home page of the app is called. Also notice the <b>otel.scope.name as Microsoft.AspNetCore</b>. Check the below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-homepage-trace.png" alt="Jaeger Home Page Traces" title="Jaeger Home Page Traces" class="img-fluid"></p>
<p>I have successfully integrated Jaeger to my ASP.NET Core app.</p>
<h2>SQL Server Tracing in Jaeger + ASP.NET Core</h2>
<p>To collect SQL Server traces and export to Jaeger, I first need to add the NuGet package called <span class="term">OpenTelemetry.Instrumentation.SqlClient</span>. So I run the below given dotnet add package command:</p>



<pre class="wp-block-code"><code>dotnet add package OpenTelemetry.Instrumentation.SqlClient</code></pre>



<p>Next, I enable Enable SqlClient Instrumentation in the Program class with the following code line &#8211; <span class="term">AddSqlClientInstrumentation()</span>. Check the highlighted code below:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [5]; title: ; notranslate">
otel.WithTracing(tracing =&gt;
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSqlClientInstrumentation();     
    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =&gt;
        {
            otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
        });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
</pre></div>


<p>This will record the following:</p>
<ul>
<li>error type</li>
<li>namespace</li>
<li>operation name</li>
<li>query summary</li>
<li>query text</li>
<li>response status code</li>
<li>stored procedure name</li>
<li>system name</li>
<li>server address</li>
<li>server port</li>
</ul>
<p>With this configuration in place, when the app fetched the records from the SQL Server database. The SQL Server traces are recorded and can be viewed on Jaeger. On Jaeger UI, I can see details like:</p>
<ul>
<li><b>db.system.name</b>       microsoft.sql\_server</li>
<li><b>otel.scope.name</b>	OpenTelemetry.Instrumentation.SqlClient</li>
</ul>
<p>To test it, I added a Read action method in the HomeController.cs which reads Employee records from the database. See below code.</p>



<pre class="wp-block-code"><code>public async Task&lt;IActionResult> Read()
{
    var employees = context.Employee;
    return View(employees);
}</code></pre>



<p>Next, on Jaeger I can see the traces along with the SQL Select query that ran against the database.</p>



<pre class="wp-block-code"><code>SELECT &#91;e].&#91;Id], &#91;e].&#91;DateOfBirth], &#91;e].&#91;Designation], &#91;e].&#91;Email], &#91;e].&#91;FirstName], &#91;e].&#91;Gender], &#91;e].&#91;LastName], &#91;e].&#91;Telephone]
FROM &#91;Employee] AS &#91;e]</code></pre>



<p>Check the below Jaeger screenshot where I have shown this information.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-sql-server-trace.png" alt="Jaeger SQL Server Trace" title="Jaeger SQL Server Trace" class="img-fluid"></p>
<p>This is very helpful in cases where there are databases errors like connection errors, sql query errors and so on. We can find the causes and location of these error and can quicky fix them. See the below image which shows error trace displayed on Jaeger. This database error came when trying to insert a record. Jaeger also displayed the url path in the app from where this db error was raised.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-db-error.png" alt="Jaeger DB Error" title="Jaeger DB Error" class="img-fluid"></p>
<div class="noteBlock">Note that the code which generates this error is given on the source codes which you can download from my GitHub repo. Link at the top.</div>
<h2>Create Custom Traces with ActivitySource class</h2>
<p>We can create custom traces or Activities with <b>ActivitySource</b> class. An Activity has an operation name, an ID, a start time and duration, tags, and baggage. I added a class named <span class="term">AppActivitySource.cs</span> inside the Models folder of the app, see it&#8217;s code below.</p>



<pre class="wp-block-code"><code>public class AppActivitySource : IDisposable
{
    public static readonly string ActivitySourceName = "AppCustomActivity";
    private readonly ActivitySource activitySource = new ActivitySource(ActivitySourceName);

    public Activity AppActivity(string activityName, ActivityKind kind = ActivityKind.Internal)
    {
        var activity = activitySource.StartActivity(activityName, kind);
        return activity;
    }

    public void Dispose()
    {
        activitySource.Dispose();
    }
}</code></pre>



<p>The above class creates an <span class="term">ActivitySource</span> object with name &#8220;AppCustomActivity&#8221;. The name is provided in a static variable so that it can be added to the <u>TracerProviderBuilder</u> object in the Program class.</p>
<p>See the <span class="term">AppActivity()</span> method which creates a new Activity using the <u>StartActivity()</u> method.</p>
<p>Now, I register the AppActivitySource class as a Singleton in the Program.cs and add it to the Trace builder. The code for this is given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [1,9]; title: ; notranslate">
builder.Services.AddSingleton&lt;AppActivitySource&gt;();

otel.WithTracing(tracing =&gt;
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSqlClientInstrumentation(); 
    tracing.AddEntityFrameworkCoreInstrumentation(); 
    tracing.AddSource(AppActivitySource.ActivitySourceName); // custom Activity

    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =&gt;
        {
            otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
        });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
</pre></div>


<p>I now create custom Activites in the Create action method of the HomeController.cs. This action method adds a new employee to the database. See the highlighted code below.</p>



<pre class="wp-block-code"><code>public class HomeController : Controller
{
    private CompanyContext context;
    private AppActivitySource appActivitySource;

    public HomeController(CompanyContext context, AppActivitySource appActivitySource)
    {
        this.context = context;
        this.appActivitySource = appActivitySource;
    }

    // other methods

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

    &#91;HttpPost]
    public async Task&lt;IActionResult> Create(Employee emp)
    {
        context.Add(emp);
        await context.SaveChangesAsync();

        using (Activity? activity = appActivitySource.AppActivity("CRUD Operations"))
        {
            activity?.SetTag("CREATE Employee", "Create Action HTTPOST");
        }

        ViewBag.Message = "Employee created successfully!";
        return View();
    }
}</code></pre>



<p>After the employee is added I create a new custom Activity so that a new trace is created with the below code.</p>



<pre class="wp-block-code"><code>using (Activity? activity = appActivitySource.AppActivity("CRUD Operations"))
{
    activity?.SetTag("CREATE Employee", "Create Action HTTPOST");
}</code></pre>



<p>On Jaeger this activity is recorded as shown by the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/custom-activity-trace-jaeger.png" alt="Jaeger Custom Activity Trace" title="Jaeger Custom Activity Trace" class="img-fluid"></p>
<h2>Entity Framework Core Tracing in Jaeger + ASP.NET Core</h2>
<p>To collect Entity Framework Core traces the process is similar to that of SQL Server. I need to add the package called <u>OpenTelemetry.Instrumentation.EntityFrameworkCore</u>. The command to run on NuGet is:</p>



<pre class="wp-block-code"><code>dotnet add package OpenTelemetry.Instrumentation.EntityFrameworkCore --version 1.15.0-beta.1</code></pre>



<p>Next, on the Program class add the following code &#8211; <span class="term">AddEntityFrameworkCoreInstrumentation()</span>. Check the highlighted code below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [6]; title: ; notranslate">
otel.WithTracing(tracing =&gt;
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSqlClientInstrumentation(); 
    tracing.AddEntityFrameworkCoreInstrumentation(); 

    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =&gt;
        {
            otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
        });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
</pre></div>


<p>Now, whenever EF core is used to communicate with the database, the traces are recorded and viewed on Jaeger. Check the below image where Jaeger is showing the traces Entity Framework Core code that fetched the records from the database.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-efcore-trace.png" alt="Jaeger EF Core Trace" title="Jaeger EF Core Trace" class="img-fluid"></p>
<h2>Web API Tracing in Jaeger + ASP.NET Core</h2>
<p>In order to collect HTTP Web API traces I need to enable AddHttpClientInstrumentation() on the program class.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [4]; title: ; notranslate">
otel.WithTracing(tracing =&gt;
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSqlClientInstrumentation(); 
    tracing.AddEntityFrameworkCoreInstrumentation(); 

    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =&gt;
        {
            otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
        });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
</pre></div>


<p>Next, when a web api is called then it&#8217;s traces are viewable on Jaeger. See the below image showing HTTP Response Status Code of 200 received from the web api.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-web-api-trace.png" alt="Jaeger Web API Trace" title="Jaeger Web API Trace" class="img-fluid"></p>
<div class="starBlock">I created a Web API and called it from the APP. You will find it on the source codes of this APP. Check my GitHub repo for this.</div>
<h2>Redis Cache Tracing in Jaeger + ASP.NET Core</h2>
<p>Redis and Jaeger are often used together in modern cloud-native architectures to manage application performance and visibility. Redis serves as a high-speed, in-memory cache to speed up data retrieval, while Jaeger provides distributed tracing to visualize how requests flow through systems, including the time spent accessing Redis.</p>
<p>Firstly I run Redis from a Docker Container. The command is given below:</p>



<pre class="wp-block-code"><code>docker run --name redis -d -p 6379:6379 redis</code></pre>



<p>Next, I install the package OpenTelemetry.Instrumentation.StackExchangeRedis for Redis Instrumentation and Microsoft.Extensions.Caching.StackExchangeRedis for working with Redis on the app. The NuGet command is given below:</p>



<pre class="wp-block-code"><code>dotnet add package OpenTelemetry.Instrumentation.StackExchangeRedis --version 1.0.0-rc9.15
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis</code></pre>



<p>After this, on the program class, I add IConnectionMultiplexer object to create a connection to the Redis server which is running from localhost:6379 url. I also register the redis cache as a service in the program class. See the below code.</p>



<pre class="wp-block-code"><code>IConnectionMultiplexer connectionMultiplexer = ConnectionMultiplexer.Connect("localhost:6379");

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.ConnectionMultiplexerFactory = () => Task.FromResult(connectionMultiplexer);
});</code></pre>



<p>Now final thing is to add the Redis service to the trace builder. See the highlighted code below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [8]; title: ; notranslate">
otel.WithTracing(tracing =&gt;
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSqlClientInstrumentation(); 
    tracing.AddEntityFrameworkCoreInstrumentation(); 
    tracing.AddSource(AppActivitySource.ActivitySourceName); 
    tracing.AddRedisInstrumentation(connectionMultiplexer); 

    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =&gt;
        {
            otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
        });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
</pre></div>


<p>I now use the IDistributedCache interface that provides the methods to manipulate items in the distributed cache implementation which in my case is Redis cache. So I create a new class called DistributedCacheExtensions.cs which provides extension methods for IDistributedCache. These extension method will provide works like reading from Cache and adding values to cache. I have not provided the class code here but you will find this class on the source code of this app, check the &#8220;Models&#8221; folder where you will find this class.</p>



<pre class="wp-block-code"><code>public static class DistributedCacheExtensions
{
    // code
}</code></pre>



<p>Let&#8217;s check the working of Redis Cache and find it&#8217;s instrumentation data on Jaeger. So I add a new action method called &#8220;ReadCache&#8221; in the HomeController.cs with the following code.</p>



<pre class="wp-block-code"><code>public async Task&lt;IActionResult> ReadCache()
{
    var employees = await GetAll();
    return View(employees);
}

public async Task&lt;List&lt;Employee>> GetAll()
{
    var cacheKey = "employees";
    var cacheOptions = new DistributedCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromMinutes(20))
            .SetSlidingExpiration(TimeSpan.FromMinutes(2));
    var products = await cache.GetOrSetAsync(
        cacheKey,
        async () =>
        {
            return await context.Employee.ToListAsync();
        },
        cacheOptions)!;
    return products!;
}</code></pre>



<p>When this action method executes, the first time there is no cache so database is called to get the records. These records are also saved on the Redis Cache so that for the next time, the records are fetched from the Cache itself.</p>
<p>Now on Jaeger, I can see the full Redis Cache traces. I have shown this in the below images.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-redis-trace-1.png" alt="Jaeger Redis Trace" title="Jaeger Redis Trace" class="img-fluid"></p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-redis-trace-2.png" alt="Jaeger Redis Trace" title="Jaeger Redis Trace" class="img-fluid"></p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/jaeger-redis-trace-3.png" alt="Jaeger Redis Trace" title="Jaeger Redis Trace" class="img-fluid"></p>
<h2>Microservices &#8211; MassTransit &#8211; RabbitMQ Cache Tracing in Jaeger + ASP.NET Core</h2>
<p>I will now implement tracing for MassTransit and RabbitMQ. I will send Messages to RabbitMQ with MassTransit so that RabbitMQ publishes the messages to the consumer.</p>
<p>First thing I have to do is to run RabbitMQ from a Docker Container. The command is given below.</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>In the app I install the RabbitMQ OpenTelemetry NuGet package:</p>



<pre class="wp-block-code"><code>dotnet add package RabbitMQ.Client.OpenTelemetry --version 1.0.0-rc.2</code></pre>



<p>Next, on the program class, I add AddRabbitMQInstrumentation() as shown below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [9]; title: ; notranslate">
otel.WithTracing(tracing =&gt;
{
    tracing.AddAspNetCoreInstrumentation();
    tracing.AddHttpClientInstrumentation();
    tracing.AddSqlClientInstrumentation(); 
    tracing.AddEntityFrameworkCoreInstrumentation(); 
    tracing.AddSource(AppActivitySource.ActivitySourceName); 
    tracing.AddRedisInstrumentation(connectionMultiplexer); 
    tracing.AddRabbitMQInstrumentation(); 

    if (tracingOtlpEndpoint != null)
    {
        tracing.AddOtlpExporter(otlpOptions =&gt;
        {
            otlpOptions.Endpoint = new Uri(tracingOtlpEndpoint);
        });
    }
    else
    {
        tracing.AddConsoleExporter();
    }
});
</pre></div>


<p>To test it, I first add the NuGet Package RabbitMQ.Client with the following command:</p>



<pre class="wp-block-code"><code>dotnet add package RabbitMQ.Client</code></pre>



<p>Next, I add SendMessage action in the HomeController which sends the message to RabbitMQ.</p>



<pre class="wp-block-code"><code>public async Task&lt;IActionResult> SendMessage(Employee emp)
{
    var factory = new ConnectionFactory { HostName = "localhost" };
    using var connection = await factory.CreateConnectionAsync();
    using var channel = await connection.CreateChannelAsync();

    await channel.QueueDeclareAsync(queue: "hello", durable: false, exclusive: false, autoDelete: false,
        arguments: null);

    const string message = "Hello World!";
    var body = Encoding.UTF8.GetBytes(message);

    await channel.BasicPublishAsync(exchange: string.Empty, routingKey: "hello", body: body);

    return RedirectToAction("Index");
}</code></pre>



<p>The traces of the messages are viewed in Jaeger. Check the below screenshot.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/03/masstransit-rabbitmq-jaeger.png" alt="Jaeger MassTransit RabbitMQ Trace" title="Jaeger MassTransit RabbitMQ Trace" class="img-fluid"></p>
<p>Microservices architecture is a more complex distributed trace. Take for example the case of an Online Shopping website, I am sending a request to get the customer&#8217;s cart. This request will first hit the API gateway, which proxies it to the Microservice that owns the data. Next this microservice calls another microservice to get the authorization information. And all of this leads to the distributed trace that Jaeger will display in a beautiful manner.</p>
<div class="note">Conclusion</div>
<p>In this tutorial I covered how to use Jaeger in ASP.NET Core. I explained by to capture traces of SQL Server, Entity Framework Core, ActivitySource, Web API, Redis Cache, RabbitMQ and other things. You can download the source codes of this tutorial from my GitHub account, link given at the top.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-opentelemetry-jaeger/">How to integrate OpenTelemetry with Jaeger for Distributed Tracing 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-opentelemetry-jaeger/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to perform Health checks in ASP.NET Core</title>
		<link>https://www.yogihosting.com/aspnet-core-health-checks/</link>
					<comments>https://www.yogihosting.com/aspnet-core-health-checks/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Sat, 28 Feb 2026 05:11:52 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22707</guid>

					<description><![CDATA[<p>In the world of software development, health checks are automated diagnostic &#8220;pulses&#8221; that verify whether an application is functioning correctly. Unlike simple uptime monitors, a comprehensive health check doesn&#8217;t just ask, &#8220;Is the app on?&#8221;—it asks, &#8220;Is the app actually ready to work?&#8221; This involves probing specific endpoints (like /healthz) to validate internal components such [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-health-checks/">How to perform Health checks in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In the world of software development, health checks are automated diagnostic &#8220;pulses&#8221; that verify whether an application is functioning correctly. Unlike simple uptime monitors, a comprehensive health check doesn&#8217;t just ask, &#8220;Is the app on?&#8221;—it asks, &#8220;Is the app actually ready to work?&#8221; This involves probing specific endpoints (like /healthz) to validate internal components such as database connectivity, memory usage, and third-party API responsiveness.</p>
<div class="note">Why They Matter</div>
<p>Proactive Recovery: Systems like Kubernetes use these checks to identify &#8220;zombie&#8221; processes that are running but frozen, automatically restarting them to minimize downtime.</p>
<p>Smart Traffic Routing: Load balancers like Nginx use health checks to divert traffic away from struggling servers, ensuring users only hit healthy instances.</p>
<p>Early Detection: They catch issues—like a disconnected database or a full disk—before they escalate into a site-wide outage.</p>
<div class="starBlock">The full source codes for the Health Checks app which we will build in this tutorial can be downloaded from <a href="https://github.com/yogyogi/Health-checks-in-ASP.NET-Core">this GitHub repository</a>.</div>
<h2>Add Health Checks in ASP.NET Core Apps</h2>
<p>In ASP.NET Core app a basic heath check can be added to process requests (liveness) of the app. In the Program class import the namespace called &#8220;Microsoft.Extensions.Diagnostics.HealthChecks&#8221; then add <code>builder.Services.AddHealthChecks()</code> and <code>app.MapHealthChecks("/healthz")</code> as shown below.</p>



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


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [4,8]; title: ; notranslate">
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks(&quot;/healthz&quot;);

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


<p>This creates a health check endpoint at /healthz. If we open this endpoint on the browser we will see Healthy text in plain text. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/healthz-url-aspnet-core.png" alt="Healthz URL ASPNET Core" title="Healthz URL ASPNET Core" class="img-fluid"></p>
<h2>Create Custom Health Checks in ASP.NET Core</h2>
<p>To implement Health checks, create a class and implement the <span class="term">IHealthCheck</span> interface. The interface method called <span class="term">CheckHealthAsync</span> returns a HealthCheckResult that indicates the health of the app as Healthy, Degraded, or Unhealthy. The result is written as a plaintext response with a configurable status code. Check the below sample:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class MyappHealthCheck : IHealthCheck
{
    public Task&lt;HealthCheckResult&gt; CheckHealthAsync(
        HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        var isHealthy = true;

        // code to check the app and its necessary services. Set isHealthy variable to false if there is some service not working

        if (isHealthy)
        {
            return Task.FromResult(
                HealthCheckResult.Healthy(&quot;My App is Healthy&quot;));
        }

        return Task.FromResult(
            new HealthCheckResult(
                context.Registration.FailureStatus, &quot;My App is Un-Healthy&quot;));
    }
}
</pre></div>


<p>If CheckHealthAsync throws an exception during the check, a new HealthCheckResult is returned with a FailureStatus message.</p>
<p>Health check probes are mechanisms used to determine the state of an application running inside a container. They help ensure reliability, automatic recovery, and proper traffic routing. There are 3 types of probes &#8211; Liveness Probe, Readiness and Startup. These are explained in the below table.</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Type</th>
<th>Purpose</th>
<th>Action Taken</th>
</tr>
</thead>
<tbody>
<tr>
<td>Liveness</td>
<td>Checks if the app is alive or in a deadlock.</td>
<td>Restarts the app / container.</td>
</tr>
<tr>
<td>Readiness</td>
<td>Checks if the app is running normally but isn&#8217;t ready to receive requests.</td>
<td>If it fails, removes the app from the load balancer pool. Traffic stops until it becomes ready again.</td>
</tr>
<tr>
<td>Startup</td>
<td>Checks if slow-starting apps have finished initializing.</td>
<td>Delays liveness/readiness probes to prevent early restarts.</td>
</tr>
</tbody>
</table>
</div>
<div class="starBlock">Containerization systems like Docker needs health checks to ensure apps run properly. I have written <a href="https://www.yogihosting.com/category/docker/">Complete Docker Series for ASP.NET Core apps</a>. Makes sure to check it.</div>
<p>See the below chart to understand the comparison between these probes.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/health-probes-comparison.png" alt="Health Probes Comparison" title="Health Probes Comparison" class="img-fluid"></p>
<p>In order to add these 3 probes &#8211; Liveness Probe, Readiness and Startup, to our .NET app. We have to Register these health check probes in the Program.cs and then create health check endpoints for each of these 3 probes. Check the highlighted code below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [7,8,9,10,11,12,13,14,15,16,17,18,19,20,31,32,33,34,35,36,37,38,39,40,41,42]; title: ; notranslate">
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews();

builder.Services.AddHealthChecks()
    .AddCheck&lt;MyHealthCheckStartup&gt;(
        &quot;Startup&quot;,
        tags: new&#x5B;] { &quot;sp_tag&quot; });

builder.Services.AddHealthChecks()
    .AddCheck&lt;MyHealthCheckLiveness&gt;(
        &quot;Liveness&quot;,
        tags: new&#x5B;] { &quot;lp_tag&quot; });

builder.Services.AddHealthChecks()
    .AddCheck&lt;MyHealthCheckReadiness&gt;(
        &quot;Readiness&quot;,
        tags: new&#x5B;] { &quot;rp_tag&quot; });

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(&quot;/Home/Error&quot;);
    app.UseHsts();
}

app.MapHealthChecks(&quot;/healthz/SP&quot;, new HealthCheckOptions()
{
    Predicate = (check) =&gt; check.Tags.Contains(&quot;sp_tag&quot;)
});
app.MapHealthChecks(&quot;/healthz/LP&quot;, new HealthCheckOptions()
{
    Predicate = (check) =&gt; check.Tags.Contains(&quot;lp_tag&quot;)
});
app.MapHealthChecks(&quot;/healthz/RP&quot;, new HealthCheckOptions()
{
    Predicate = (check) =&gt; check.Tags.Contains(&quot;rp_tag&quot;)
});

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

app.UseAuthorization();

app.MapStaticAssets();

app.MapControllerRoute(
    name: &quot;default&quot;,
    pattern: &quot;{controller=Home}/{action=Index}/{id?}&quot;)
    .WithStaticAssets();

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


<p>In the above code we create 3 endpoints for Startup, Liveness and Readiness probes. These endpoints are:</p>



<pre class="wp-block-code"><code>/healthz/SP 
/healthz/LP
/healthz/RP</code></pre>



<p>These endpoints are implemented with 3 separate C# classes which are:</p>
<ol>
<li>MyHealthCheckStartup.cs</li>
<li>MyHealthCheckLiveness.cs</li>
<li>MyHealthCheckReadiness.cs</li>
</ol>
<p>By default, the Health Checks Middleware runs all registered health checks. To filter health checks for running a subset of health checks, provide a function that returns a boolean to the Predicate option. Here we applied filters to the health checks so that only those tagged with &#8220;sp_tag&#8221; runs for endpoint &#8220;/healthz/SP&#8221;, tagged with &#8220;lp_tag&#8221; runs for &#8220;/healthz/LP&#8221; and tagged with &#8220;rp_tag&#8221; runs for &#8220;/healthz/RP&#8221;. See the below codes:</p>



<pre class="wp-block-code"><code>app.MapHealthChecks("/healthz/SP", new HealthCheckOptions()
{
    Predicate = (check) =&gt; check.Tags.Contains("sp_tag")
});

app.MapHealthChecks("/healthz/LP", new HealthCheckOptions()
{
    Predicate = (check) =&gt; check.Tags.Contains("lp_tag")
});

app.MapHealthChecks("/healthz/RP", new HealthCheckOptions()
{
    Predicate = (check) =&gt; check.Tags.Contains("rp_tag")
});</code></pre>



<p>Next, we add the following 3 classes to our app which implements the 3 probes &#8211; Startup, Liveness &#038; Readiness.</p>
<h3>Implementation of Startup Probe</h3>
<p>An app takes some initial time to start because it does some initial tasks like running DB migrations, warm up caches, load large configurations, initialize background services, compile codes and so on. It is necessary to not send traffic to the app until it has fully started.</p>
<p>In the below class we have implemented the Startup Probe in a class which does the initial work, and only when this work is successfully finished the Healthy HealthCheckResult is returned.</p>



<pre class="wp-block-code"><code>public class MyHealthCheckStartup : IHealthCheck
{
    public Task&lt;HealthCheckResult&gt; CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        if (StartupWork.DoWork())
        {
            return Task.FromResult(HealthCheckResult.Healthy("The startup task has completed."));
        }

        return Task.FromResult(HealthCheckResult.Unhealthy("That startup task is still running."));
    }
}</code></pre>



<p>The StartupWork.DoWork() checks the status of the initial work and will get a bool value of true or false telling whether this work has completed or not. The StartupWork.cs class code is given below, it does the checking of the work progress. Although we are demonstrating this by applying a time delay of 10 seconds so that in these 10 seconds the work completes. During these first 10 seconds the startup probe will fail since it returns &#8220;Unhealthy&#8221; status and after 10 seconds it completes successfully because it will then return &#8220;Healthy&#8221; status.</p>
<p>In real world app, you have to add your own code which does the complete checks and returns bool value of true when the initial works have finished.</p>



<pre class="wp-block-code"><code>public class StartupWork
{
    private static readonly DateTime _startTime = DateTime.UtcNow;
    public static bool DoWork()
    {
        // here check the status of the initial work
        if ((DateTime.UtcNow - _startTime).TotalSeconds &gt;= 10)
        {
            return true;
        }

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



<p>The apps hosted on Kubernetes runs the startup probe first and until it succeeds the Liveness and Readiness probes are disabled.</p>
<p>Once Startup probe succeeds, then only the liveness/readiness probes begin. If the startup probe fails too many times → the container is restarted.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/startup-probe.png" alt="Startup Probe" title="Startup Probe" class="img-fluid"></p>
<h3>Implementation of Readiness Probe &#8211; Example of Database Probe</h3>
<p>Readiness probes ensure the application is functional and ready to serve traffic, making them ideal for checking dependency availability. For example we should check whether the database is responding normally or not.</p>
<p>For checking the database function choose a query that returns quickly. Ideal query is &#8211; SELECT 1 since it completes quickly in the database. We defined MyHealthCheckReadiness class which performs the Readiness Health Check probe to test the database. The SeELECT 1 query is executed against the database, if successful we return <code>HealthCheckResult.Healthy("The Database is working properly")</code> else in case of failure we return <code>HealthCheckResult.Unhealthy(ex.Message)</code>. Check the below code.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
public class MyHealthCheckReadiness : IHealthCheck
{
    private readonly string _connectionString;

    public MyHealthCheckReadiness(IConfiguration configuration)
    {
        _connectionString = configuration.GetConnectionString(&quot;Database&quot;);
    }

    public Task&lt;HealthCheckResult&gt; CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            using var sqlConnection = new SqlConnection(_connectionString);

            sqlConnection.OpenAsync(cancellationToken);

            using var command = sqlConnection.CreateCommand();
            command.CommandText = &quot;SELECT 1&quot;;

            command.ExecuteScalarAsync(cancellationToken);

            return Task.FromResult(HealthCheckResult.Healthy(&quot;The Database is working properly&quot;));
        }
        catch (Exception ex)
        {
            return Task.FromResult(HealthCheckResult.Unhealthy(ex.Message));
        }
    }
}
</pre></div>


<p>We can also use an existing Health Check Libraries to perform this SQL Server health check. Include a package reference to the AspNetCore.HealthChecks.SqlServer NuGet package. Then on the program class add the below code.</p>



<pre class="wp-block-code"><code>var conStr = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddHealthChecks()
    .AddSqlServer(conStr);</code></pre>



<p>If the app is using Entity Framework Core then include a package reference to the Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore NuGet package. Next, add the following code to the program class.</p>



<pre class="wp-block-code"><code>builder.Services.AddDbContext&lt;SampleDbContext&gt;(options =&gt;
    options.UseSqlServer(
        builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddHealthChecks()
    .AddDbContextCheck&lt;SampleDbContext&gt;();</code></pre>



<p>There are Health Check Libraries available for popular programs some examples are given below.</p>
<ul>
<li>SQL Server &#8211; AspNetCore.HealthChecks.SqlServer</li>
<li>Postgres &#8211; AspNetCore.HealthChecks.Npgsql</li>
<li>Redis &#8211; AspNetCore.HealthChecks.Redis</li>
<li>RabbitMQ &#8211; AspNetCore.HealthChecks.RabbitMQ</li>
<li>AWS S3 &#8211; AspNetCore.HealthChecks.Aws.S3</li>
<li>SignalR &#8211; AspNetCore.HealthChecks.SignalR</li>
</ul>
<p>The Readiness probe for RabbitMQ is given below.</p>



<pre class="wp-block-code"><code>builder.Services.AddHealthChecks().AddRabbitMQ(rabbitConnectionString)</code></pre>



<h3>Implementation of Liveness Probe</h3>
<p>The Liveness health check probe should only check internal app health. It should figure out if the app is still alive, or is it stuck/broken.</p>
<p>Common problem for the Liveness probe to fail are due to:</p>
<ul>
<li>Deadlocks</li>
<li>Infinite loops</li>
<li>Crashed background workers</li>
<li>Memory corruption</li>
<li>Thread pool exhaustion</li>
</ul>
<p>Below class does the Liveness probe. You can update this sample based on your specific needs.</p>



<pre class="wp-block-code"><code>public class MyHealthCheckLiveness : IHealthCheck
{
    public Task&lt;HealthCheckResult&gt; CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        if (LivenessWork.DoWork())
        {
            return Task.FromResult(HealthCheckResult.Healthy("The startup task has completed."));
        }

        return Task.FromResult(HealthCheckResult.Unhealthy("That startup task is still running."));
    }
}</code></pre>



<h2>Health Checks in Docker and Kubernetes</h2>
<p>Docker can detect if your application inside the container is running properly or not. In the Dockerfile we can add HEALTHCHECK as given below.</p>



<pre class="wp-block-code"><code>FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY . .
ENTRYPOINT &#91;"dotnet", "MyApp.dll"]

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost/health || exit 1</code></pre>



<p>It says run a curl command &#8211; Every 30 seconds and wait for 5 seconds to check the app. If it fails 3 times → mark container unhealthy.</p>
<p>The below code is for docker-compose.yml file. Here <span class="term">start_period</span> gives the app time to start before health checks begin.</p>



<pre class="wp-block-code"><code>services:
  myapp:
    image: myapp:latest
    ports:
      - "5000:8080"
    healthcheck:
      test: &#91;"CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s</code></pre>



<p>To restart unhealthy container use restart policy.</p>



<pre class="wp-block-code"><code>docker run -d --restart=always --name my_container my_image</code></pre>



<p>The Docker Compose version is:</p>



<pre class="wp-block-code"><code>version: '3'
services:
  myservice:
    image: my-image
    restart: always</code></pre>



<p>In Kubernetes, health checks (probes) are mechanisms that let Kubernetes determine whether your container:</p>
<ol>
<li>Has started correctly</li>
<li>Is ready to receive traffic</li>
<li>Is still running properly</li>
</ol>
<p>Kubernetes uses probes to automatically manage container lifecycle and traffic routing, container restarts and so on. We have written a complete article on <a href="https://www.yogihosting.com/kubernetes-liveness-readiness-startup-probes/">Kubernetes Liveness Readiness Startup Probes</a> which you will find quite useful.</p>
<p>The Kubernetes yml file that defines the health checks are given below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47]; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-dep
  labels:
    app: aspnet-core-app
spec:
  replicas: 1
  selector:
    matchLabels:
      component: web
  template:
    metadata:
      labels:
        component: web
    spec:
      containers:
        - name: csimpleweb
          image: simpleweb
          imagePullPolicy: Never
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /healthz/SP
              port: 80
            failureThreshold: 25
            periodSeconds: 10
          
          livenessProbe:
            httpGet:
              path: /healthz/LP
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 10

          readinessProbe:
            httpGet:
              path: /healthz/RP
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 10
            successThreshold: 5
</pre></div>


<h2>Health Check Publisher</h2>
<p>a Health Check Publisher is a background component that periodically runs health checks and publishes the results somewhere (logs, metrics system, monitoring tool, etc.). It runs automatically in the background.</p>
<p>The work of the Health Check Publisher is to:</p>
<ul>
<li>Push health status to monitoring</li>
<li>Log unhealthy states automatically</li>
<li>Send alerts</li>
<li>Export metrics to Prometheus</li>
<li>Publish to Azure Application Insights</li>
</ul>
<p>The Health Check Publisher must implement <span class="term">IHealthCheckPublisher</span> interface of Microsoft.Extensions.Diagnostics.HealthChecks namespace. See the below sample:</p>



<pre class="wp-block-code"><code>using Microsoft.Extensions.Diagnostics.HealthChecks;

public class SampleHealthCheckPublisher : IHealthCheckPublisher
{
    public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
    {
        if (report.Status == HealthStatus.Healthy)
        {
            // ...
        }
        else
        {
            // ...
        }

        return Task.CompletedTask;
    }
}</code></pre>



<p>We also need to registers the health check publisher as a singleton and configures HealthCheckPublisherOptions:</p>



<pre class="wp-block-code"><code>builder.Services.Configure&lt;HealthCheckPublisherOptions&gt;(options =&gt;
{
    options.Delay = TimeSpan.FromSeconds(20);       // Initial delay
    options.Period = TimeSpan.FromSeconds(100);     // Run every 100s
    options.Predicate = healthCheck =&gt; healthCheck.Tags.Contains("sample"); // run a subset of health checks i.e. here tagged with sample
});

builder.Services.AddSingleton&lt;IHealthCheckPublisher, SampleHealthCheckPublisher&gt;();</code></pre>



<p>What You Get in &#8216;HealthReport&#8217; object &#8211; Overall status, Individual check results, Duration, Exception details and Custom data. See the below code.</p>



<pre class="wp-block-code"><code>
foreach (var entry in report.Entries)
{
    Console.WriteLine($"{entry.Key} - {entry.Value.Status}");
}</code></pre>



<h2>Formatting Health Checks Response</h2>
<p>By default, the health check endpoint returns a simple string that represents the overall HealthStatus. To format it in order to see the messages in json which is easy to understand. You can provide a custom ResponseWriter. A ready-made implementation is available in the AspNetCore.HealthChecks.UI.Client library, which returns a more detailed and structured health report.</p>
<p>Install the NuGet package:</p>



<pre class="wp-block-code"><code>Install-Package AspNetCore.HealthChecks.UI.Client</code></pre>



<p>Update the call to MapHealthChecks to use the ResponseWriter as shown below.</p>



<pre class="wp-block-code"><code>app.MapHealthChecks(
    "/healthz",
    new HealthCheckOptions
    {
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });</code></pre>



<p>Now the response from the health check endpoint looks like:</p>



<pre class="wp-block-code"><code>{
  "status": "Unhealthy",
  "totalDuration": "00:00:00.987769",
  "entries": {
    "npgsql": {
      "data": {},
      "duration": "00:00:00.2424424",
      "status": "Healthy",
      "tags": &#91;]
    },
    "rabbitmq": {
      "data": {},
      "duration": "00:00:00.21412",
      "status": "Healthy",
      "tags": &#91;]
    },
    "myapp-sql": {
      "data": {},
      "description": "Unable to connect to the DB.",
      "duration": "00:00:00.2424242",
      "exception": "Unable to connect to the DB.",
      "status": "Unhealthy",
      "tags": &#91;]
    }
  }
}</code></pre>



<div class="note">Conclusion</div>
<p>Health checks in .NET applications are an essential part of building reliable, production-ready systems. They provide visibility into the state of your application and its dependencies, enabling platforms like Kubernetes, load balancers, and monitoring tools to respond appropriately to failures. In this tutorial we covered everything related to building a health check system for your .NET app. Hope you liked it and if you have any ideas make sure to post them by using the below given comments section.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-health-checks/">How to perform Health checks 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-health-checks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to perform Logging in ASP.NET Core</title>
		<link>https://www.yogihosting.com/aspnet-core-logging/</link>
					<comments>https://www.yogihosting.com/aspnet-core-logging/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Fri, 13 Feb 2026 05:43:07 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22652</guid>

					<description><![CDATA[<p>Logging is an essential part of ASP.NET Core apps which help us to monitor app behavior and diagnose problems. By default the following 4 providers are added whenever we create a .NET app. These are: Console &#8211; displays logs on console window, helpful in local development phase of the app for monitoring and debugging in [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-logging/">How to perform Logging in ASP.NET Core</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Logging is an essential part of ASP.NET Core apps which help us to monitor app behavior and diagnose problems. By default the following 4 providers are added whenever we create a .NET app. These are:</p>



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



<ol>
<li><span class="term">Console</span> &#8211; displays logs on console window, helpful in local development phase of the app  for monitoring and debugging in real time.</li>
<li><span class="term">Debug</span> &#8211; writes logs using the System.Diagnostics.Debug class. Used mainly in local development of the app.</li>
<li><span class="term">EventSource</span> &#8211; this provider writes to a cross-platform event source with the name Microsoft-Extensions-Logging.</li>
<li><span class="term">Windows EventLog</span> &#8211; this provider writes logs to Windows Event Log.</li>
</ol>
<p>All the logs created by default logging providers are displayed in console window and Debug output window when debugging. When running the application using the <code>dotnet run</code> command from a terminal, logs are displayed directly in the command shell.</p>
<div class="note">Logging Example</div>
<p>In Visual Studio create a new .NET app by selecting <u>ASP.NET Core Web App (Model-View-Controller)</u> template.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2022/02/ASP.NET-Core-Web-App-MVC.png" alt="create a new asp.net core web application" title="create a new asp.net core web application" class="img-fluid"></p>
<p>Open the <span class="term">appsettings.development.json</span> to find the default configurations in json format given as follows:</p>



<pre class="wp-block-code"><code>{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}</code></pre>



<p>It configures logging at Information level for every category and at Warning level for Microsoft.AspNetCore category. We will see more about it later on. If you run the app we can see the logs displayed on the console, see below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/default-logging-console.png" class="img-fluid" alt="Default Logging Console" title="Default Logging on Console"></p>
<p>Here dbug (stands for Debug) and info (stands for Information) on the left, these are the log levels. Log level indicates the severity of the logged event. The middle part where we have Microsoft.Hosting.Lifetime is the category and 14 inside the bracket ([14]) is the Event ID. Each log message can specify an Event ID through which filtering on the ID can be done.</p>
<p>In the app we use an <span class="term">ILogger&lt;TCategoryName></span> object on the constructor of the controller to inject ILogger object from dependency injection (DI). The TCategoryName is the log category which is a string that is associated with each log. It is usually the name of the class where the logs are written. The log category is useful for identifying, sorting, and filtering log messages.</p> 
<p>Update the HomeController code, to inject ILogger object through DI and then log at the Information level.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [12]; title: ; notranslate">
public class HomeController : Controller
{
    private readonly ILogger&lt;HomeController&gt; logger;

    public HomeController(ILogger&lt;HomeController&gt; log)
    {
        logger = log;
    }

    public IActionResult Index()
    {
        logger.LogInformation(&quot;Testing logging&quot;);
        return View();
    }
}
</pre></div>


<p>When the Index action executes, you will find the logs as shown below:</p>



<pre class="wp-block-code"><code>info: LoggingExample.Controllers.HomeController&#91;0]
      Testing logging</code></pre>



<p>Here LoggingExample.Controllers.HomeController is the category and 0 inside the bracket ([0]) is the Event ID. Each log message can specify an Event ID through which filtering on the ID can be done.</p>
<p>See the below image where the logs are displayed on the Console window and Debug output window.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/console-debug-windows-logs.png" class="img-fluid" alt="Debug Logs Console" title="Debug Logs on Console"></p>
<p>In the below code logging for the exception is done in the catch block. Here 400 is an event ID which can be anything of your liking.</p>



<pre class="wp-block-code"><code>try
{
   ...

   throw new Exception("Test exception");
}
catch (Exception ex)
{
    logger.LogWarning(400, ex.Message, DateTime.UtcNow);
}</code></pre>



<h2>Configure logging on appsettings.json file</h2>
<p>We can provide logging configuration on the appsettings.json file. Note that for production we write the configurations on <span class="term">appsettings.json</span> and for development the configurations goes to <span class="term">appsettings.development.json</span> file.</p>
<p>Open the <span class="term">appsettings.development.json</span> to find the default configurations in json format given as follows:</p>



<pre class="wp-block-code"><code>{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}</code></pre>



<p>The JSON data is written as name/value pairs. It consists of a field name (in double quotes), followed by a colon, followed by its value. The first field is &#8220;Logging&#8221; which contains child field inside it. Here it is &#8220;LogLevel&#8221;. The LogLevel specifies the minimum level to log for the categories i.e. logging is enabled at the specified level and higher (more severe). More about LogLevel in the below section.</p>
<p>See the LogLevel field on the json as given above. It contains 2 category fields &#8211; &#8220;Default&#8221; and &#8220;Microsoft.AspNetCore&#8221; and their values are given as &#8220;Information&#8221; and &#8220;Warning&#8221;. So this means logging is done at &#8220;Information&#8221; level for the &#8220;Default&#8221; category (i.e. every category other that Microsoft.AspNetCore), and at &#8220;Warning&#8221; level for the &#8220;Microsoft.AspNetCore&#8221; category.</p>
<p>It should also be noted that in the above case no provider is specified so this applies to every providers.<p>
<p>When LogLevel is given under a provider name then the configurations apply for that provider only. These settings overrides the non-provider log settings. Consider the following.</p>



<pre class="wp-block-code"><code>{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    },
    "EventLog": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  }
}</code></pre>



<p>Here Windows EventLog provider is added so the configurations overrides the non-provider log settings. This means now for EventLog, logging for Default category will be done at Warning level and for Microsoft.AspNetCore category at Warning level. Note that since EventLog does not have &#8220;Microsoft.AspNetCore&#8221; category so it is inherited from the non-provider log settings given above.</p>
<h2>Log Level</h2>
<p>The Log Levels indicate the severity of the log. The LogLevel specifies the minimum level to log for the categories i.e. logging is enabled at the specified level and higher (more severe). These 7 log levels from 0 to 6 are given below.</p>
<div class="table-responsive">
    <table class="table table-striped table-bordered">
        <thead>
            <tr class="table-primary">
                <th>Log Level</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Trace</td>
                <td>0</td>
            </tr>
            <tr>
                <td>Debug</td>
                <td>1</td>
            </tr>
            <tr>
                <td>Information</td>
                <td>2</td>
            </tr>
            <tr>
                <td>Warning</td>
                <td>3</td>
            </tr>
            <tr>
                <td>Error</td>
                <td>4</td>
            </tr>
            <tr>
                <td>Critical</td>
                <td>5</td>
            </tr>
            <tr>
                <td>None</td>
                <td>6</td>
            </tr>
        </tbody>
    </table>
</div>
<p><span class="term">Trace</span> &#8211; trace contain the most detailed messages which also contain sensitive data that should not be made available on the production. So by default it is disabled. In the development phase of the app, it can be very useful for debugging purposes.</p>
<p><span class="term">Debug</span> &#8211; it is popularly used for debugging and development. Use with caution in production due to the high volume of messages logged.</p>
<p><span class="term">Information</span> &#8211; used for logging general flow of the app.</p>
<p><span class="term">Warning</span> &#8211; used for abnormal or unexpected events that do not cause the app to fail.</p>
<p><span class="term">Errors</span> &#8211; used for unhandled errors and exception that cause the app failure.</p>
<p><span class="term">Critical</span> &#8211; used for severe errors that requires immediate attention. Examples &#8211; data loss or out of disk space.</p>
<p><span class="term">None</span> &#8211; specifies not to write messages.</p>
<p>The important methods to log messages are:</p>



<pre class="wp-block-code"><code>logger.Log(LogLevel.Information, 400, "Testing"); // specify the log level as the first parameter followed by the event Id then at the last the message

Logger.LogTrace("Testing"); // log at trace level

Logger.LogDebug("Testing"); // log at debug level

Logger.LogInformation("Testing"); // log information

logger.LogWarning(400, "Test exception at {DT}", DateTime.UtcNow); // log at warning level with event id 400

Logger.LogError("Testing"); // log at error level

Logger.LogCritical("Testing"); // log at critical level</code></pre>



<h2>Logging Providers</h2>
<p>The Logging Providers help to write logs on a given medium. ASP.NET Core includes the following logging providers by default:</p>
<ol>
<li>Console</li>
<li>Debug</li>
<li>EventSource</li>
<li>Windows EventLog</li>
</ol>
<p>Third party providers are also available and can be installed from NuGet. Some examples includes AzureAppServicesFile, ApplicationInsights, etc.</p>
<p>Logging providers are configured on <span class="term">appsettings.json</span> and <span class="term">appsettings.development.json</span> depending upon the environment.</p>
<p>Open the <span class="term">appsettings.development.json</span> file to find the default configuration containing non-provider section, see below.</p>



<pre class="wp-block-code"><code>{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}</code></pre>



<p>We can activate other providers by adding their configurations to  this file.</p>
<h3>Console</h3>
<p>The Console provider only displays logs on the console. The logs are not persisted once the app stops. The console logs are useful when running an app locally for monitoring and debugging in real time. On the <span class="term">appsettings.development.json</span> file we can add the console section to configure console logging provider. See below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [7,8,9,10,11,12,13]; title: ; notranslate">
{
  &quot;Logging&quot;: {
    &quot;LogLevel&quot;: {
      &quot;Default&quot;: &quot;Information&quot;,
      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;
    },
    &quot;Console&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Warning&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor.Razor&quot;: &quot;Debug&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor&quot;: &quot;Error&quot;
      }
    }
  }
}
</pre></div>


<p>The configuration specifies that for Console, every category is set as &#8220;Warning&#8221; except for categories Microsoft.AspNetCore.Mvc.Razor.Razor at Debug level and Microsoft.AspNetCore.Mvc.Razor at Error level.</p>
<p>When a specific category is listed, the specific category overrides the default value.</p>
<p>If you now look to the console, very few logs are displayed. This is because Warning for Default category is quite high and lower level logs are not written on the console. These are given below.</p>



<pre class="wp-block-code"><code>dbug: Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine&#91;1]
      =&gt; SpanId:f0377c2fb682381f, TraceId:debbe69a510e3a5336d79ce744429ac0, ParentId:0000000000000000 =&gt; ConnectionId:0HNJ9IFE8CN2C =&gt; RequestPath:/ RequestId:0HNJ9IFE8CN2C:00000001 =&gt; LoggingExample.Controllers.HomeController.Index (LoggingExample)
      View lookup cache miss for view 'Index' in controller 'Home'.
dbug: Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine&#91;1]
      =&gt; SpanId:f0377c2fb682381f, TraceId:debbe69a510e3a5336d79ce744429ac0, ParentId:0000000000000000 =&gt; ConnectionId:0HNJ9IFE8CN2C =&gt; RequestPath:/ RequestId:0HNJ9IFE8CN2C:00000001 =&gt; LoggingExample.Controllers.HomeController.Index (LoggingExample)
      View lookup cache miss for view '_Layout' in controller 'Home'.</code></pre>



<p>Change the &#8220;Default&#8221;: &#8220;Warning&#8221; to &#8220;Default&#8221;: &#8220;Trace&#8221; for the console provider. Now rerun the app and see on the console. You will find hundreds of log message. See below.</p>



<pre class="wp-block-code"><code>dbug: Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactory&#91;12]
      Registered model binder providers, in the following order: Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ServicesModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.HeaderModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FloatingPointTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.EnumTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DateTimeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.SimpleTypeModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.TryParseModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CancellationTokenModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ByteArrayModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormFileModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormCollectionModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.KeyValuePairModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DictionaryModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinderProvider, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexObjectModelBinderProvider
dbug: Microsoft.Extensions.Hosting.Internal.Host&#91;1]
      Hosting starting
info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager&#91;63]
      User profile is available. Using 'C:\Users\Avita\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
dbug: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository&#91;37]
      Reading data from file 'C:\Users\Avita\AppData\Local\ASP.NET\DataProtection-Keys\key-e075d172-f7d1-4435-895d-14ee7bf094e4.xml'.
dbug: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository&#91;37]
      Reading data from file 'C:\Users\Avita\AppData\Local\ASP.NET\DataProtection-Keys\key-f24b36ed-9ad7-42f7-ba4e-18e72008ecf2.xml'.
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager&#91;18]
      Found key {e075d172-f7d1-4435-895d-14ee7bf094e4}.
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager&#91;18]
      Found key {f24b36ed-9ad7-42f7-ba4e-18e72008ecf2}.
dbug: Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver&#91;13]
      Considering key {e075d172-f7d1-4435-895d-14ee7bf094e4} with expiration date 2026-03-26 11:10:11Z as default key.
dbug: Microsoft.AspNetCore.DataProtection.TypeForwardingActivator&#91;0]
      Forwarded activator type request from Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor, Microsoft.AspNetCore.DataProtection, Version=10.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 to Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor, Microsoft.AspNetCore.DataProtection, Culture=neutral, PublicKeyToken=adb9793829ddae60
dbug: Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor&#91;51]
      Decrypting secret element using Windows DPAPI.</code></pre>



<p>Certainly this can be useful when you want to dig deep into the app&#8217;s debugging process.</p>
<p>Whatever logs we write from any logging methods (examples below), these are all displayed on the console. Provided the log level are equal or higher to that configured on the appsettings file.</p>



<pre class="wp-block-code"><code>logger.Log(LogLevel.Information,400, "Testing");
logger.LogTrace("TestT");
logger.LogDebug("TestD");
logger.LogInformation("Testing logging");</code></pre>



<h3>Debug</h3>
<p>The Debug provider writes log output by using the System.Diagnostics.Debug class and only when the debugger is attached. We can use <span class="term">System.Diagnostics.Debug.WriteLine()</span> method to write logs with Debug provider. On Linux, the Debug provider log location is one of the following:</p>



<pre class="wp-block-code"><code>/var/log/message
/var/log/syslog</code></pre>



<p>This provider is useful for developers to monitor application flow, identify errors, and check variable states during development phase of the app.</p> 
<p>In the below configuration we added the Debug provider block which sets the default category to Debug level.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [14,15,16,17,18]; title: ; notranslate">
{
  &quot;Logging&quot;: {
    &quot;LogLevel&quot;: {
      &quot;Default&quot;: &quot;Information&quot;,
      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;
    },
    &quot;Console&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Warning&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor.Razor&quot;: &quot;Debug&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor&quot;: &quot;Error&quot;
      }
    },
    &quot;Debug&quot;: { 
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Information&quot; 
      }
    }
  }
}
</pre></div>


<p>The Debug provider configuration will inherit the non-provider category &#8220;Microsoft.AspNetCore&#8221; value of Warning.</p>
<p>The below code line writes log message with the Debug provider.</p>



<pre class="wp-block-code"><code>System.Diagnostics.Debug.WriteLine("Test Debug Provider", "LoggingExample.Controllers.HomeController");</code></pre>



<h3>EventSource</h3>
<p>The EventSource provider writes to a cross-platform event source with the name Microsoft-Extensions-Logging.</p>
<p>On the <span class="term">appsettings.development.json</span> add the EventSource configurations as shown below.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [19,20,21,22,23]; title: ; notranslate">
{
  &quot;Logging&quot;: {
    &quot;LogLevel&quot;: {
      &quot;Default&quot;: &quot;Information&quot;

    },
    &quot;Console&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Warning&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor.Razor&quot;: &quot;Debug&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor&quot;: &quot;Error&quot;
      }
    },
    &quot;Debug&quot;: { 
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Trace&quot; 
      }
    },
    &quot;EventSource&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Information&quot;
      }
    }
  }
}
</pre></div>


<p>This configuration tells ASP.NET Core to write logs that are at Information level with the EventSource provider. We can add more configurations to it but for the sake of simplicity I have kept things simple to understand.</p>
<p>To read the logs written by EventSource provider we use a tool called <span class="term">dotnet-trace</span>. Lets understand how to use it.</p>
<h3>dotnet-trace</h3>
<p>To install dotnet-trace tool, open a terminal from the directory of the project file and run the below given command:</p>



<pre class="wp-block-code"><code>dotnet tool install --global dotnet-trace</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/install-dotnet-trace.png" alt="Install Dotnet Trace" title="Install Dotnet Trace" class="img-fluid"></p>
<p>Now to view the logs perform the following tasks:</p>
<ol>
<li>Run the app from the CLI command &#8211; <span class="term">dotnet run</span>. For this open a terminal from the directory of the project file (.csproj) and run the command from there.</li>
<li>Find the process identifier (PID) of the .NET app. For this you have to run <span class="term">dotnet-trace ps</span> command. Note that the PID for the process has the same name as the app&#8217;s assembly.</li>
<li>Open another terminal and run the command &#8211; <span class="term">dotnet-trace collect -p %PID% &#8211;providers Microsoft-Extensions-Logging</span>. Replace %PID% with the PID of the .NET app.</li>
</ol>
<p>In the below image, I am running the <span class="term">dotnet run</span> command from the terminal.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/dotnet-run.png" alt="Dotnet Run" title="Dotnet Run" class="img-fluid"></p>
<p>Once the app is running, from another terminal I run the <span class="term">dotnet-trace ps</span> command which gave me the PID of the .NET app as 15348. I next run the <span class="term">dotnet-trace collect -p 15348 &#8211;providers Microsoft-Extensions-Logging</span> command to see the logs. See the below image where I have shown this thing.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/dotnet-trace-logs.png" alt="Dotnet Trace Logs" title="Dotnet Trace Logs" class="img-fluid"></p>
<p>The full syntax of dotnet-trace command is given below.</p>



<pre class="wp-block-code"><code>dotnet-trace collect -p {PID} --providers Microsoft-Extensions-Logging:{KEYWORD}:{PROVIDER LEVEL}:FilterSpecs=\"{LOGGER CATEGORY 1}:{CATEGORY LEVEL 1}; {LOGGER CATEGORY 2}:{CATEGORY LEVEL 2}; ...
{LOGGER CATEGORY N}:{CATEGORY LEVEL N}\"</code></pre>



<p>The following table defines the keyword {KEYWORD} placeholder.</p>
<div class="table-responsive">
    <table class="table table-striped table-bordered">
        <thead>
            <tr class="table-primary">
                <th>Keyword</th>
                <th>Description</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>Logs meta events</td>
            </tr>
            <tr>
                <td>2</td>
                <td>Provides information in a programmatic manner</td>
            </tr>
            <tr>
                <td>4</td>
                <td>Provides formatted string version of the information</td>
            </tr>
            <tr>
                <td>8</td>
                <td>Provides a JSON representation of the information</td>
            </tr>
        </tbody>
    </table>
</div>
<p>The below table gives the provider levels.</p>

<div class="table-responsive">
    <table class="table table-striped table-bordered">
        <thead>
            <tr class="table-primary">
                <th>Provider Level</th>
                <th>Description</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>0</td>
                <td>LogAlways</td>
            </tr>
            <tr>
                <td>1</td>
                <td>Critical</td>
            </tr>
            <tr>
                <td>2</td>
                <td>Error</td>
            </tr>
            <tr>
                <td>3</td>
                <td>Warning</td>
            </tr>
            <tr>
                <td>4</td>
                <td>Informational</td>
            </tr>
            <tr>
                <td>5</td>
                <td>Verbose</td>
            </tr>
        </tbody>
    </table>
</div>
<p>FilterSpecs is used to filter the logs based on the categories and their log levels. Multiple FilterSpecs entries can be added with the ; semicolon character between them</p>
<p>Examples:</p>



<pre class="wp-block-code"><code>dotnet-trace collect -p %PID% --providers Microsoft-Extensions-Logging:4:2:FilterSpecs=\"Microsoft.AspNetCore.Hosting*:Debug\"
dotnet-trace collect -p %PID%  --providers Microsoft-Extensions-Logging:4:5:\"Microsoft.AspNetCore.Hosting*:Debug; Microsoft.AspNetCore.Mvc*:Information;\"</code></pre>



<h3>Windows EventLog</h3>
<p>The Windows EventLog provider writes log  to the Windows Event Log. Unlike the other providers, the EventLog provider doesn&#8217;t inherit the default non-provider settings. If EventLog log configurations aren&#8217;t specified, they default to LogLevel.Warning. In order to log lower than LogLevel.Warning, explicitly set them with the log level.</p>
<p>The following example sets the Event Log default log level to LogLevel.Information:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; highlight: [24,25,26,27,28]; title: ; notranslate">
{
  &quot;Logging&quot;: {
    &quot;LogLevel&quot;: {
      &quot;Default&quot;: &quot;Information&quot;

    },
    &quot;Console&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Warning&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor.Razor&quot;: &quot;Debug&quot;,
        &quot;Microsoft.AspNetCore.Mvc.Razor&quot;: &quot;Error&quot;
      }
    },
    &quot;Debug&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Trace&quot;
      }
    },
    &quot;EventSource&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Default&quot;: &quot;Information&quot;
      }
    },
    &quot;EventLog&quot;: {
      &quot;LogLevel&quot;: {
        &quot;Microsoft&quot;: &quot;Information&quot;
      }
    }
  }
}
</pre></div>


<p>Open <span class="term">Event Viewer</span> on your windows OS to view the logs saved there. Check the below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/02/Event-Viewer.png" alt="Windows Event Viewer" title="Windows Event Viewer" class="img-fluid"></p>
<p>Note that when storing logs on Windows Event Log then makes sure only the high priority logs should be stored since it will unnecessary slow down the app and the server.</p>
<h3>Third Party log providers</h3>
<p>Various 3rd party providers writes logs to various mediums like  Microsoft.Extensions.Logging.AzureAppServices writes logs to text files in an Azure App Service file system and blob storage.</p>
<p>Microsoft.Extensions.Logging.ApplicationInsights provider logs to Azure Application Insights.</p>
<p>Other popular log providers are NLog and Serilog.</p>
<h2>HTTP logging in ASP.NET Core</h2>
<p>We can also log incoming HTTP requests and HTTP responses to ASP.NET Core apps using HTTP logging. Some of the features of HTTP logging are:</p>
<ul>
<li>Log HTTP request, HTTP response, Headers and Body.</li>
<li>Log only requests and responses that meet certain criteria and also select parts of request or response to log.</li>
<li>For security redact sensitive information from the logs.</li>
</ul>
<h3>How to enable HTTP Logging in ASP.NET Core apps</h3>
<p>In the Program class add the <span class="code">AddHttpLogging</span> and <span class="code">UseHttpLogging</span> methods as shown below.</p>


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

// Add services to the container.
builder.Services.AddControllersWithViews();

builder.Services.AddHttpLogging(o =&gt; { });

var app = builder.Build();

app.UseHttpLogging();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(&quot;/Home/Error&quot;);
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

...
</pre></div>


<p>Also add Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware to the appsettings.Development.json file at the &#8220;LogLevel&#8221;.</p>



<pre class="wp-block-code"><code>{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Trace"
    }
  }
}</code></pre>



<p>The HTTP logs will now appear on the console as shown below.</p>



<pre class="wp-block-code"><code>info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware&#91;1]
      Request:
      Protocol: HTTP/2
      Method: GET
      Scheme: https
      PathBase:
      Path: /
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
      Host: localhost:7211
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
      Accept-Encoding: gzip, deflate, br, zstd
      Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
      Cookie: &#91;Redacted]
      Upgrade-Insecure-Requests: &#91;Redacted]
      sec-ch-ua: &#91;Redacted]
      sec-ch-ua-mobile: &#91;Redacted]
      sec-ch-ua-platform: &#91;Redacted]
      sec-fetch-site: &#91;Redacted]
      sec-fetch-mode: &#91;Redacted]
      sec-fetch-user: &#91;Redacted]
      sec-fetch-dest: &#91;Redacted]
      priority: &#91;Redacted]</code></pre>



<p>We can also configure logging through lambda expressions as shown below.</p>



<pre class="wp-block-code"><code>builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});</code></pre>



<p>Here HttpLoggingOptions.LoggingFields is an enum flag that configures specific parts of the request and response to log. RequestHeaders and ResponseHeaders are sets of HTTP headers that are logged. Header values are only logged for header names that are in these collections.</p>
<p>MediaTypeOptions provides configuration for selecting which encoding to use for a specific media type. Setting CombineLogs to true configures the middleware to consolidate all of its enabled logs for a request and response into one log at the end.</p>
<p>Http logging with redaction can be enabled by calling AddRedaction and AddHttpLoggingRedaction on the program class.</p>



<pre class="wp-block-code"><code>builder.Services.AddRedaction();
builder.Services.AddHttpLoggingRedaction(op => { });</code></pre>




<p>Redaction helps you sanitize or mask sensitive information in logs, error messages, or other outputs. This keeps you compliant with privacy rules and protects sensitive data. It&#8217;s useful in apps that handle personal data, financial information, or other confidential data points.</p>
<div class="note">Conclusion</div>
<p>In this tutorial we covered logging in ASP.NET Core from complete beginning till advanced levels. It will help you to debug various problems coming on .NET apps.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-logging/">How to perform Logging 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-logging/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</title>
		<link>https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/</link>
					<comments>https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Wed, 04 Feb 2026 06:27:01 +0000</pubDate>
				<category><![CDATA[ASP.NET Core apps in Docker]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22608</guid>

					<description><![CDATA[<p>In this tutorial we will Deploy an ASP.NET Core Dockerized App to Azure Container Apps using GitHub Actions CI / CD pipeline. The full working of the process is described in the below image: First we will push our ASP.NET Core app to GitHub repository which will trigger the Workflow. The Workflow will basically do [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/">Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial we will Deploy an ASP.NET Core Dockerized App to Azure Container Apps using GitHub Actions CI / CD pipeline. The full working of the process is described in the below image:</p>



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



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-container-apps-github-actions.png" class="img-fluid" alt="Azure Container Apps GitHub Actions" title="Azure Container Apps GitHub Actions"></p>
<p>First we will push our ASP.NET Core app to GitHub repository which will trigger the Workflow. The Workflow will basically do 3 tasks:</p>
<ol>
<li>Build a Docker Image for the ASP.NET Core from the Dockerfile.</li>
<li>Push the Docker Image to Azure Container Registry (ACR).</li>
<li>Deploy the Azure Container Apps with the Docker Image in ACR.</li>
</ol>
<div class="expandableCollapsibleDiv">
<img decoding="async" src="https://www.yogihosting.com/wp-content/themes/yogi-yogihosting/Images/down-arrow.jpg">
<h4>This tutorial is a part of <b>ASP.NET Core apps on Docker</b> series.</h4>
<ul style="display:block">
<li>1. <a target="_blank" href="https://www.yogihosting.com/docker-aspnet-core-app/" rel="noopener">Create first ASP.NET Core App in a Docker Container</a></li>
<li>2. <a target="_blank" href="https://www.yogihosting.com/docker-aspnet-core-azure/" rel="noopener">Deploy a Docker based ASP.NET Core app to Azure</a></li>
<li>3. <a href="https://www.yogihosting.com/docker-https-aspnet-core/" target="_blank">ASP.NET Core APP with HTTPS in Docker</a></li>
<li>4. <a target="_blank" href="https://www.yogihosting.com/docker-compose-aspnet-core/" rel="noopener">Multi-Container ASP.NET Core App with Docker Compose</a></li>
<li>5. <a target="_blank" href="https://www.yogihosting.com/docker-aspnet-core-sql-server-crud/" rel="noopener">CRUD Operations in ASP.NET Core and SQL Server with Docker</a></li>
<li>6. <a target="_blank" href="https://www.yogihosting.com/docker-volumes-aspnet-core/" rel="noopener">Docker Volumes on ASP.NET Core App</a></li>
<li>7. <a href="https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/" target="_blank">Configuring Nginx as Reverse Proxy and Load Balancer for Dockerized ASP.NET Core apps</a></li>
<li>8. <i>Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</i></li>
</ul>
</div>
<h2>ASP.NET Core App</h2>
<p>The .NET app is a Quiz based app which is shown below:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/dotnet-app-github-actions.png" alt=".Net App GitHub Actions" title=".Net App GitHub Actions" class="img-fluid"></p>
<p>You can get this full app along with the GitHub Workflow from my <a href="https://github.com/yogyogi/QuizCA" target="_blank">GitHub Repository</a>.</p>
<h2>Create Azure Container Registry</h2>
<p>First, we will create an Azure Container Registry which will store the docker image for this app. Later the GitHub Actions CI/CD will push the docker image to this repository. So create a new Azure Container Repository and call it <u>QuizAppACR</u>.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/acr.png" class="img-fluid" alt="Azure Container Registry" title="Azure Container Registry"></p>
<p>Note that we have used &#8220;Role assignment permissions mode&#8221; to &#8220;RBAC Registry Permissions&#8221;. We will create this permission next. Basically this permission is needed for pushing the Docker Image of the APP from GitHub CI / CD pipeline to ACR.</p>
<div class="noteBlock">Want to make your data persistent in Docker then read &#8211; <a href="https://www.yogihosting.com/docker-volumes-aspnet-core/" target="_blank">Docker Volumes on ASP.NET Core App</a></div>
<p>After the registry is created enable the Admin user from &#8220;Settings > Access keys&#8221;. Check the below screenshot.</p>
<img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/acr-admin-user.png" alt="ACR Admin User" title="ACR Admin User" class="img-fluid"></p>
<p>Admin user is needed for using the Image for Container Apps when we create the app from Azure portal.</p>
<p>We now have to Create <u>Credentials for Azure Authentication</u> for GitHub Actions to push Docker Image of the app to Azure Container Registry. We have to run 3 commands on Azure CLI (given below). First login to azure using the Azure CLI command <u>az login</u> on the command prompt or terminal. Note that you have to install Azure CLI on your pc. Check the <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest" target="_blank">Download Link</a>.</p>
<p>After login run the below 3 commands one by one on.</p>
<div class="note">1. Get the resource ID of your Resource Group</div>



<pre class="wp-block-code"><code>az group show --name &lt;resource-group-name&gt; --query id --output tsv</code></pre>



<p>Change <span class="term"><resource-group-name></span> to your resource group name. In my case it is R1 so this below command I run, check the below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-resource-id.png" class="img-fluid" alt="Azure Resource Id" title="Azure Resource Id"></p>
<p>The command will give the resource ID.</p>



<pre class="wp-block-code"><code>/subscriptions/73435ad5-0765-7664-CV56-62hy02436a7d/resourceGroups/R1</code></pre>



<p>We will use this resource id in the next command.</p>
<div class="note">2. Create the Service Principal</div>
<p>The next command is to create the service principal from the below command.</p>



<pre class="wp-block-code"><code>az ad sp create-for-rbac --scope $groupId --role Contributor --sdk-auth</code></pre>



<p>Here change the <span class="term">$groupId</span> with the resource Id we got in the previous step. In my case my command becomes:</p>



<pre class="wp-block-code"><code>az ad sp create-for-rbac --scope /subscriptions/73435ad5-0765-7664-CV56-62hy02436a7d/resourceGroups/R1 --role Contributor --sdk-auth</code></pre>



<p>The command will give a JSON,  save the JSON output because it&#8217;s used in a later step. Also, take note of the clientSecret and clientId, which you need to update the service principal in the next section.</p>



<pre class="wp-block-code"><code>{
  "clientId": "6334i778-5352-434c-a469-40332d7e63a9",
  "clientSecret": "HdY8Q~jj4EynmuGjgD5c7BCKMCI-U2XbUB2jqcm_",
  "subscriptionId": "73435ad5-0765-7664-CV56-62hy02436a7d",
  "tenantId": "404c4c76-767d-6s89-b7c9-ad800d5433ea",
  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
  "resourceManagerEndpointUrl": "https://management.azure.com/",
  "activeDirectoryGraphResourceId": "https://graph.windows.net/",
  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
  "galleryEndpointUrl": "https://gallery.azure.com/",
  "managementEndpointUrl": "https://management.core.windows.net/"
}</code></pre>



<p>Check the below image showing the output of this command.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-service-principal.png" class="img-fluid" alt="Azure Service Principal" title="Azure Service Principal"></p>
<div class="note">3. Update for Registry Authentication</div>
<p>We now get the ACR registry Id from the below command.</p>



<pre class="wp-block-code"><code>az acr show --name &lt;registry-name&gt; --resource-group &lt;resource-group-name&gt; --query id --output tsv</code></pre>



<p>We change the <span class="term">&lt;registry-name></span> and <span class="term">&lt;resource-group-name></span> from our values. This is the updated command in our case.</p>



<pre class="wp-block-code"><code>az acr show --name QuizAppACR --resource-group R1 --query id --output tsv</code></pre>



<p>Check the screenshot below.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/acr-registry-id.png" class="img-fluid" alt="Azure ACR ID" title="Azure ACR ID"></p>
<p>Next, we update for registry authentication. We assign the AcrPush role, which gives push and pull access to the registry. The command is given below.</p>



<pre class="wp-block-code"><code>az role assignment create --assignee &lt;ClientId&gt; --scope $registryId --role AcrPush</code></pre>



<p>Substitute the client ID of your service principal and registryId in the below command.</p>



<pre class="wp-block-code"><code>az role assignment create --assignee 6334i778-5343452-434c-a469-40332d7e63a9 --scope /subscriptions/74d23ad6-5454-4236-ab57-62b342dd2wtrrtd/resourceGroups/R1/providers/Microsoft.ContainerRegistry/registries/QuizAppACR --role AcrPush</code></pre>



<p>We have now created our registry authentication, see the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-registry-authentication.png" class="img-fluid" alt="Azure ACR Authentication" title="Azure ACR Authentication"></p>
<h2>Create GitHub Actions Workflow</h2>
<p>Create a GitHub Workflow file called <u>dotnet.yml</u> inside the <u>.github\workflows</u> folder of the app. Add the following code to this file:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
 
name: .NET
 
on:
  push:
    branches: &#x5B; &quot;main&quot; ]
  pull_request:
    branches: &#x5B; &quot;main&quot; ]
 
jobs:
  build:
 
    runs-on: ubuntu-latest
 
    steps:
    - uses: actions/checkout@v4
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 10.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal --logger trx --results-directory &quot;TestResults&quot;
    - name: Upload dotnet test results
      uses: actions/upload-artifact@v4
      with:
        name: dotnet-results-${{ matrix.dotnet-version }}
        path: TestResults
        # Use always() to always run this step to publish test results when there are test failures
      if: ${{ always() }}
     
    - name: Login via Azure CLI
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS }}
         
    - name: Build and push image&#039;
      uses: azure/docker-login@v1
      with:
        login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
        username: ${{ secrets.REGISTRY_USERNAME }}
        password: ${{ secrets.REGISTRY_PASSWORD }}
    - run: | 
        docker build -f Quiz/Dockerfile -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }} .
        docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }}
</pre></div>


<div class="note">Explanation</div>
<p>The Workflow task includes setting a runner machine with .NET 10.0.</p>



<pre class="wp-block-code"><code>- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: 10.0.x</code></pre>



<p>Next, the tasks like running the project restore, building the project and running the test are done. These are done through dotnet commands &#8211; <code>dotnet restore, dotnet build and dotnet test</code> commands.</p>
<div class="starBlock">DevOps is incomplete without Nginx, see &#8211; <a href="https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/" target="_blank">Configuring Nginx as Reverse Proxy and Load Balancer for Dockerized ASP.NET Core apps</a></div>
<p>The next task performs the login to Azure through the JSON output from the service principal creation step. We will add this json to GitHub secret variable called &#8220;AZURE_CREDENTIALS&#8221;.</p>



<pre class="wp-block-code"><code>- name: Login via Azure CLI
  uses: azure/login@v1
  with:
    creds: ${{ secrets.AZURE_CREDENTIALS }}</code></pre>



<p>The next task performs the building of the docker image and pushing the image to our Azure Container Registry. Here we used 3 GitHub secrets &#8211; REGISTRY_LOGIN_SERVER, REGISTRY_USERNAME and REGISTRY_PASSWORD.</p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Secret</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>REGISTRY_LOGIN_SERVER</td>
<td>quizappacr.azurecr.io</td>
</tr>
<tr>
<td>REGISTRY_USERNAME</td>
<td>The client id value received on the JSON which is 6334i778-5352-434c-a469-40332d7e63a9</td>
</tr>
<tr>
<td>REGISTRY_PASSWORD</td>
<td>The client secret value received on the JSON which is HdY8Q~jj4EynmuGjgD5c7BCKMCI-U2XbUB2jqcm_</td>
</tr>
</tbody>
</table>
</div>
<p>The code is give below.</p>



<pre class="wp-block-code"><code>- name: Build and push image'
  uses: azure/docker-login@v1
  with:
    login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
    username: ${{ secrets.REGISTRY_USERNAME }}
    password: ${{ secrets.REGISTRY_PASSWORD }}
- run: | 
    docker build -f Quiz/Dockerfile -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }} .
    docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }}</code></pre>



<p>Note the docker build command has -f flag where we have specified the location of Dockerfile. The image name will be <span class="term">quizappacr.azurecr.io/quizca</span> followed by the GitHub sha as image tag.</p>
<h2>Create GitHub Repository with Repository Secrets</h2>
<p>We now create a GitHub repository with 4 GitHub secrets containing the values given below. Secrets can be added from <span class="term">Settings > Secrets and variables > Actions</span>.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-repository-secrets.png" class="img-fluid" alt="GitHub Repository Secrets" title="GitHub Repository Secrets"></p>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr class="table-primary">
<th>Secret</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>AZURE_CREDENTIALS</td>
<td>The entire JSON output from the service principal creation step.</td>
</tr>
<tr>
<td>REGISTRY_LOGIN_SERVER</td>
<td>quizappacr.azurecr.io</td>
</tr>
<tr>
<td>REGISTRY_USERNAME</td>
<td>The client id value received on the JSON which is 6334i778-5352-434c-a469-40332d7e63a9</td>
</tr>
<tr>
<td>REGISTRY_PASSWORD</td>
<td>The client secret value received on the JSON which is HdY8Q~jj4EynmuGjgD5c7BCKMCI-U2XbUB2jqcm_</td>
</tr>
</tbody>
</table>
</div>
<p>Its time to push the full code of our app to our GitHub repository. You can use git push command or GitHub Desktop to do this work. The Workflow execution will trigger automatically and it finishes successfully. See the below image of the workflow.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-workflow-run.png" class="img-fluid" alt="GitHub Workflow run" title="GitHub Workflow run"></p>
<p>The Workflow builds the docker image in the runner virtual machine and pushes the image to the Azure Container Registry. Open the registry where you will find the image. Check the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/acr-repository-docker-image.png" class="img-fluid" alt="ACR Repository Docker Image" title="ACR Repository Docker Image"></p>
<h2>Create Azure Container Apps</h2>
<p>We now create an Azure Container Apps that will use the Docker image from our Azure Container Registry. We named the app &#8220;quizapp&#8221; and selected &#8220;Azure Container Registry&#8221; for Image source. Then select the registry, Image and Image Tag which contains the docker image for our app.</p>
<p>For the Authentication type select &#8220;Managed identity&#8221; and for the &#8220;Managed identity&#8221; select <u>System assigned Identity (environment)</u>. Check the below image where we have shown this thing.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-container-apps.png" class="img-fluid" alt="Azure Container Apps" title="Azure Container Apps"></p>
<p>Also check the Ingress check box and select the Ingress traffic value as &#8220;Accepting traffic from anywhere&#8221;.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-container-apps-ingress.png" class="img-fluid" alt="Azure Container Apps Ingress" title="Azure Container Apps Ingress"></p>
<p>The app is now ready and opening on the browser. See the below image:</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-app-services-dotnet.png" alt="Azure App Services .NET" title="Azure App Services .NET" class="img-fluid"></p>
<p>You may be wondering that we have not yet applied the CI / CD pipeline on the app&#8217;s deployment. Although we have applied CI / CD on the docker image pushing from GitHub to ACR. Don&#8217;t worry this is the topic next.</p>
<h2>Add GitHub Actions CI / CD for App Deployment to Azure</h2>
<p>Add the below task on the Workflow file at the end. This will Deploy the app to Azure Container app with the image in ACR. The acrName is specified as the name of the ACR repository which is QuizAppACR, the containerAppName value is quizapp. Resource Group is given R1 for our case (choose your own resource group). Then on the imageToDeploy, we provide the latest image which is find out with the current github.sha code.</p>



<pre class="wp-block-code"><code>- name: Build and deploy Container App
  uses: azure/container-apps-deploy-action@v1
  with:
    acrName: QuizAppACR 
    containerAppName: quizapp
    resourceGroup: R1
    imageToDeploy: ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }}</code></pre>



<p>We also change the heading on the apps html to include &#8220;UPDATED&#8221; text. Push the new app file and the workflow file to GitHub once again. This will push the latest updated apps code. Which you can see in the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-app-update-cicd-github.png" class="img-fluid" alt="Azure APP Update CICD GitHub" title="Azure APP Update CICD GitHub"></p>
<p>So the confirms our CICD is working and it is now also covering the CI/CD pipeline for the app&#8217;s deployment to azure.</p>
<p>This is the full GitHub Workflow file code:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
 
name: .NET
 
on:
  push:
    branches: &#x5B; &quot;main&quot; ]
  pull_request:
    branches: &#x5B; &quot;main&quot; ]
 
jobs:
  build:
 
    runs-on: ubuntu-latest
 
    steps:
    - uses: actions/checkout@v4
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 10.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal --logger trx --results-directory &quot;TestResults&quot;
    - name: Upload dotnet test results
      uses: actions/upload-artifact@v4
      with:
        name: dotnet-results-${{ matrix.dotnet-version }}
        path: TestResults
        # Use always() to always run this step to publish test results when there are test failures
      if: ${{ always() }}
     
    - name: Login via Azure CLI
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS }}
         
    - name: Build and push image&#039;
      uses: azure/docker-login@v1
      with:
        login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
        username: ${{ secrets.REGISTRY_USERNAME }}
        password: ${{ secrets.REGISTRY_PASSWORD }}
    - run: | 
        docker build -f Quiz/Dockerfile -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }} .
        docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }}
 
    - name: Build and deploy Container App
      uses: azure/container-apps-deploy-action@v1
      with:
        acrName: QuizAppACR 
        containerAppName: quizapp
        resourceGroup: R1
        imageToDeploy: ${{ secrets.REGISTRY_LOGIN_SERVER }}/quizca:${{ github.sha }}
</pre></div>


<div class="note">Conclusion</div>
<p>In this tutorial we covered in full details how to create GitHub Action CI / CD pipeline for ASP.NET Core containerized app. The pipeline builds the docker image of the app in a virtual machine on GitHub Runner. Then pushes the docker image to ACR and deploys this ACR image to the app. I hope you liked this tutorial, if any questions then use the comments section below.</p>
<p>The post <a href="https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/">Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Deploy ASP.NET Core app to Azure with GitHub Actions CI/CD</title>
		<link>https://www.yogihosting.com/aspnet-core-github-actions-cicd-azure/</link>
					<comments>https://www.yogihosting.com/aspnet-core-github-actions-cicd-azure/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Mon, 26 Jan 2026 03:51:19 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22589</guid>

					<description><![CDATA[<p>GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows to automate build, test, and deployment of our apps. In this tutorial I will deploy an ASP.NET Core app to Azure App Services through GitHub actions CI/CD deployment pipeline. GitHub Action GitHub Actions is a CI/CD platform which is integrated into your [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-github-actions-cicd-azure/">Deploy ASP.NET Core app to Azure with GitHub Actions CI/CD</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows to automate build, test, and deployment of our apps. In this tutorial I will deploy an ASP.NET Core app to Azure App Services through GitHub actions CI/CD deployment pipeline.</p>
<h2>GitHub Action</h2>
<p>GitHub Actions is a CI/CD platform which is integrated into your GitHub repository. This means you can run a CI/CD pipeline right from your GitHub repository. GitHub Actions are organized into Workflows, which are automated process that will run one or more jobs. A common example of a Workflow is to automatically build and deploy your app to Azure whenever the app is pushed to the GitHub repository.</p>
<p>The GitHub Actions kick off based on Events. Events can be anything like a push to the repository or a pull request, and so on.</p>
<p>Runners are the machines that execute jobs defined in a GitHub Actions Workflow. For example, a UBUNTU runner machine can clone your repository locally, install testing software, and then runs the tests.</p>
<p>In the below figure GitHub actions working is explained. An Event kicks the Runners that executes the different Jobs defined in the WorkFlows.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-actions-architecture.webp" alt="GitHub Actions Architecture" title="GitHub Actions Architecture" class="img-fluid"></p>



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



<p>Workflows are defined by a YAML file checked in to the repository. Their location is <span class="term">.github/workflows</span> directory located on the root of the repository.</p>
<p>The CICD process is explained in the below figure. We will push the app to the GitHub repository, this will trigger the Workflow. The Workflow will build the app and deploy the app&#8217;s published files to Azure App Services.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-cicd.png" alt="GitHub Actions CICD" title="GitHub Actions CICD" class="img-fluid"></p>
<h2>Example ASP.NET Core app</h2>
<p>I have an ASP.NET Core Quiz app which asks the users 4 questions. The questions are shown inside a multi-page form. This is how this app looks.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/dotnet-app-github-actions.png" alt=".Net App GitHub Actions" title=".Net App GitHub Actions" class="img-fluid"></p>
<p>You can download the full app along with the Workflow from my <a target="_blank" href="https://github.com/yogyogi/Quiz" target="_blank">GitHub repository</a>.</p>
<p>This app contains 2 projects:</p>
<ol>
<li>ASP.NET Core MVC project &#8211; which contains the quiz.</li>
<li>Console project &#8211; which contains the unit tests.</li>
</ol>
<p>The below image shows the full app with it&#8217;s 2 projects.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/dotnet-app-test-project-github-actions.png" alt=".Net App Test Project GitHub Actions" title=".Net App Test Project GitHub Actions" class="img-fluid"></p>
<p>I will automate a CI/CD pipeline with GitHub Actions for this app and deploy this app to Azure App Services. Note that there are some unit tests which the Workflow will run on runner machines and will only deploy the app when all the tests pass. GitHub Actions will not deploy the app if any of the test fails.</p>
<h2>Create an Azure App Services</h2>
<p>Create a new App Service on the Azure account. Make sure to select the <u>Publish</u> to &#8220;Code&#8221; and <u>Runtime stack</u> to &#8220;.NET 10 (LTS)&#8221;. Check the below image.</p> 
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-app-service-donet.png" alt="Azure App Service .NET" title="Azure App Service .NET" class="img-fluid"></p>
<p>After the app is created, I have to enable Basic Authentication for this app. This is a necessary step needed to <span class="term">Download publish profile</span>. Go to <span class="term">Settings > Configuration</span> inside the app section on Azure and click the &#8220;General settings&#8221; tab. Here select the option <u>SCM Basic Auth Publishing Credentials</u> and click the apply button. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-app-service-authentication.png" alt="Azure App Service Authentication" title="Azure App Service Authentication" class="img-fluid"></p>
<p>Now go to the app overview page and here you will see &#8220;Download publish profile&#8221; button at the top. Click it to download this file. It is an XML file containing authentication values which GitHub will use to authenticate itself when deploying the app to azure.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-download-publish-profile.png" alt="Azure Download Publish Profile" title="Azure Download Publish Profile" class="img-fluid"></p>
<p>This is all we have to do on Azure, we now move to GitHub.</p>
<h2>GitHub Repository and Workflow file</h2>
<p>We first have to create a <span class="term">.GitHub</span> folder on the root of the app. Inside this folder create another folder called <span class="term">workflows</span>. Then inside this folder create a new file called <span class="term">dotnet.yml</span>. The file name could be anything but should have an extension .yml or .yaml.</p>
<p>In the below image I have shown the workflows directory.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-actions-workflow-directory.png" alt="GitHub Actions Workflow Directory" title="GitHub Actions Workflow Directory" class="img-fluid"></p>
<p>Let&#8217;s now add the Workflow codes to the <span class="term">dotnet.yml</span> file. The full code of this file is given below.</p>



<pre class="wp-block-code"><code># This workflow will build a .NET app and deploys it to Azure

name: .NET APP

on:
  push:
    branches: &#91; "main" ]
  pull_request:
    branches: &#91; "main" ]

env:
  AZURE_WEBAPP_NAME: "QUIZ"

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 10.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal --logger trx --results-directory "TestResults"
    - name: Upload dotnet test results
      uses: actions/upload-artifact@v4
      with:
        name: dotnet-results-${{ matrix.dotnet-version }}
        path: TestResults
        # Use always() to always run this step to publish test results when there are test failures
      if: ${{ always() }}
    - name: Publish
      run: dotnet publish ./Quiz/Quiz.csproj --configuration Release --output ./publish
    - name: Deploy
      uses: azure/webapps-deploy@v3
      with: 
        app-name: ${{ env.AZURE_WEBAPP_NAME }} 
        publish-profile: ${{ secrets.PUBLISH_SECRET }}
        package: ./publish</code></pre>



<p>Lets go through it part by part so that it becomes easy for you to understand it.</p>
<div class="note">Workflow Name and Trigger</div>



<pre class="wp-block-code"><code># This workflow will build a .NET app and deploys it to Azure

name: .NET APP

on:
  push:
    branches: &#91; "main" ]
  pull_request:
    branches: &#91; "main" ]</code></pre>



<p>In the above code we named the workflow name as &#8220;.NET APP&#8221;. Name can be anything of your choice. Then with <span class="term">on</span>, I define the events which will trigger the workflow. The workflow will run on 2 events which are:</p>
<ol>
<li>when a push is made to any main branch.</li>
<li>when a pull is made to any main branch.</li>
</ol>
<div class="note">Environment Variable</div>
<p>Next, I define the environment variable using the &#8220;env&#8221; key. The environment variable is &#8220;AZURE_WEBAPP_NAME&#8221; and it&#8217;s value is given as &#8220;QUIZ&#8221;. We can access this variable using <span class="term">$env.AZURE_WEBAPP_NAME</span>, this will be see later on.</p>



<pre class="wp-block-code"><code>env:
  AZURE_WEBAPP_NAME: "QUIZ"</code></pre>



<div class="note">Workflow Jobs</div>
<p>A job is a set of steps in a workflow that is executed on the same runner machine. Jobs are defined by &#8220;jobs&#8221; key. I named this job as &#8220;build&#8221;, this name can be anything. Next, I define the virtual machine type which will run this job. This is the runner machine which in our case is Ubuntu. Note that the virtual machines are build new every single time.</p>



<pre class="wp-block-code"><code>jobs:
  build:

    runs-on: ubuntu-latest</code></pre>



<div class="note">Jobs Steps</div>
<p>A job contains a sequence of tasks called steps. Steps can run commands, setup tasks, or perform some action in your repository. I add the below code:</p>



<pre class="wp-block-code"><code>steps:
- uses: actions/checkout@v4
- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: 10.0.x
- name: Restore dependencies
  run: dotnet restore
- name: Build
  run: dotnet build --no-restore</code></pre>



<p>Let&#8217;s understand what this code does. The first task, which is <span class="term">actions/checkout@v4</span>, says to downloads the code from your repository into the virtual machine (runner) where the workflow is executing.</p>
<p>Next, the second task is defined and given the name &#8220;Setup .NET&#8221; &#8211; <span class="term">name: Setup .NET</span>. Then with the <u>uses</u> key, I define which action to run. This action is to setup dotnet on the runner &#8211; <span class="term">uses: actions/setup-dotnet@v4</span>. I also defined the version of dotnet to setup using the <u>with</u> key &#8211; <span class="term">dotnet-version: 10.0.x</span>.</p>
<p>The third task restores the dependencies of the ASP.NET Core app. It runs the <span class="term">dotnet restore</span> CLI command that uses NuGet to download and install all the dependencies (packages and tools) required by the app.</p>
<p>The fourth task build the app with <span class="term">dotnet build</span> command. The use of  &#8211;no-restore flag tells to build  a .NET project or solution without automatically performing a NuGet package restore operation first.</p>
<div class="note">Test and upload artifact</div>
<p>We now have 2 tasks that performs the testing of the app and upload the test result on a directory. These tasks are given below.</p>



<pre class="wp-block-code"><code>- name: Test
  run: dotnet test --no-build --verbosity normal --logger trx --results-directory "TestResults"
- name: Upload dotnet test results
  uses: actions/upload-artifact@v4
  with:
    name: dotnet-results-${{ matrix.dotnet-version }}
    path: TestResults
    # Use always() to always run this step to publish test results when there are test failures
  if: ${{ always() }}</code></pre>



<p>The task named <u>Test</u> runs the tests but does not builds the project &#8211; <span class="term">dotnet test &#8211;no-build</span>, this will make the process fast. It also sets the output level to normal with <u>&#8211;verbosity normal</u>. The <span class="term">&#8211;logger trx</span> will generate a Visual Studio Test Results file (.trx). This XML-based file contains the test execution results. The .trx file will be created inside the &#8220;TestResults&#8221; directory, see the parameter &#8211; <u>&#8211;results-directory &#8220;TestResults&#8221;</u>. This task&#8217;s full code is:</p>



<pre class="wp-block-code"><code>- name: Test
  run: dotnet test --no-build --verbosity normal --logger trx --results-directory "TestResults"</code></pre>



<p>The task named <u> Upload dotnet test results</u> uploads &#8220;TestResults&#8221; directory (artifacts) from the runner machine making them available to download after the workflow execution is completed. Through this you can download the test results on our pc to debug why a particular test has failed. The expression <span class="term">if: ${{ always() }}</span> is used to ensure that a job or step runs regardless of whether previous steps or jobs in the workflow succeeded, failed, or were canceled. So the upload of the test artifact will always run.</p>



<pre class="wp-block-code"><code>- name: Upload dotnet test results
  uses: actions/upload-artifact@v4
  with:
    name: dotnet-results-${{ matrix.dotnet-version }}
    path: TestResults
    # Use always() to always run this step to publish test results when there are test failures
  if: ${{ always() }}</code></pre>



<div class="note">Publish the app</div>
<p>The next task publishes the app using <span class="term">dotnet publish</span> command. The configuration is set to Release and output directory is given as &#8220;publish&#8221;.</p>



<pre class="wp-block-code"><code>- name: Publish
   run: dotnet publish ./Quiz/Quiz.csproj --configuration Release --output ./publish</code></pre>



<div class="note">Deploy the app to Azure</div>
<p>The final task is to deploy the app to Azure App service. Here I set the app-name from the environment variable <span class="code">${{ env.AZURE_WEBAPP_NAME }}</span>. I also set the publish-profile to <span class="code">${{ secrets.PUBLISH_SECRET }</span>, this will give GitHub my Azure App Services credentials so that it can deploy the published codes on &#8220;publish&#8221; folder to Azure. See the next section where I will configure this thing. We also defined the package location of the app as <span class="term">package: ./publish</span>. The code for this task is given below.</p>



<pre class="wp-block-code"><code>- name: Deploy
  uses: azure/webapps-deploy@v3
  with: 
    app-name: ${{ env.AZURE_WEBAPP_NAME }} 
    publish-profile: ${{ secrets.PUBLISH_SECRET }}
    package: ./publish</code></pre>



<h3>GitHub Secret containing Azure Publish Profile</h3>
<p>Recall I earlier downloaded the publish profile from Azure. It contains an xml file containing the Azure App Services credentials. I will use this file so that GitHub can use these credentials to deploy the app. So on the GitHub repository, go to &#8220;Setting&#8221;. Then under <u>Secrets and variables</u>, click on &#8220;Actions&#8221; and create a new secret. Give the secret any name (I named it &#8220;Publish_Secret&#8221;) and paste the contents of publish profile file on the &#8220;Secret&#8221; text box. I have shown this on the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-secrets.png" alt="GitHub Secrets" title="GitHub Secrets" class="img-fluid"></p>
<p>With the secret in place the workflow deploy task will use it through the code line &#8211; <u>publish-profile: ${{ secrets.PUBLISH_SECRET }}</u>.</p>
<h3>Push the code to GitHub Repository</h3>
<p>Let&#8217;s push the code of my app to my GitHub repository. Run the following git commands one by one on Command Prompt window.</p>



<pre class="wp-block-code"><code>// initialize git

git init
git add .
git commit -m "FirstCommit"

// push the app to GitHub repository

git remote add origin https://github.com/yogyogi/Quiz.git
git branch -M main
git push -u origin main</code></pre>



<p>After the push is completed, check the Actions tab to see the workflow automatically runs. When all the tasks finishes successfully (including the tests), the app is deployed to azure. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-workflow-build.png" alt="GitHub Workflow Build" title="GitHub Workflow Build" class="img-fluid"></p>
<p>My Workflow executed successfully. Now let&#8217;s can open the app&#8217;s url (which we get in Azure App Service). The app is now opening on the browser. See below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/azure-app-services-dotnet.png" alt="Azure App Services .NET" title="Azure App Services .NET" class="img-fluid"></p>
<p>We can also download the artifact containing the test results from the Workflow. The download link will be provided. Check the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-artifact-download.png" alt="Download Artifact" title="Download Artifact" class="img-fluid"></p>
<p>We now added a new test to our app and this test fails. Next, I push the changed to the repository. Again see the workflow whose test task fails since the <u>dotnet test</u> cli command has failed. In this case the new changes to the .NET app are not deployed to Azure by the CI/CD pipeline. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/github-workflow-build-error.png" alt="GitHub Workflow Build Error" title="GitHub Workflow Build Error" class="img-fluid"></p>
<div class="note">Conclusion</div>
<p>In this tutorial we learned how to create GitHub Actions CI/CD pipeline to deploy an ASP.NET Core app to Azure App Services. We also build the complete Workflow file and understood all the tasks it runs like creating runner, testing, building, publishing and deploying. I hope you liked this tutorial. If you face any difficult in creating your CICD then message me from the below comments section.</p>
<p>The post <a href="https://www.yogihosting.com/aspnet-core-github-actions-cicd-azure/">Deploy ASP.NET Core app to Azure with GitHub Actions CI/CD</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/aspnet-core-github-actions-cicd-azure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
