<?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>Wed, 01 Apr 2026 14:06:33 +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 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>
		<item>
		<title>Configuring Nginx as Reverse Proxy and Load Balancer for Dockerized ASP.NET Core apps</title>
		<link>https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/</link>
					<comments>https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Thu, 15 Jan 2026 04:29:23 +0000</pubDate>
				<category><![CDATA[ASP.NET Core apps in Docker]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22510</guid>

					<description><![CDATA[<p>Nginx is a high-performance, open-source web server that can also be used as a reverse proxy, load balancer, HTTP cache, and mail proxy. It is highly efficient with many simultaneous connections due to its event-driven architecture. Nginx is a core component in modern web infrastructure due to it&#8217;s reliability, speed, and scalability. Nginx is available [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/">Configuring Nginx as Reverse Proxy and Load Balancer for Dockerized ASP.NET Core apps</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Nginx is a high-performance, open-source web server that can also be used as a reverse proxy, load balancer, HTTP cache, and mail proxy. It is highly efficient with many simultaneous connections due to its event-driven architecture. Nginx is a core component in modern web infrastructure due to it&#8217;s reliability, speed, and scalability. Nginx is available for all operating systems &#8211; Windows, Linux, macOS included.</p>



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



<div id="contentTable">
<div class="title"><p class="left">Page Contents</p><p class="right"><span title="click to toggle"></span></p></div>
<nav>
<ul>
<li><a href="#install">Installing Nginx in Windows</a>
<ul>
<li><a href="#solution">Solution to Nginx starting problem in Windows</a></li>
</ul>
</li>
<li><a href="#rp">Nginx as Reverse Proxy in Dockerized ASP.NET Core app</a></li>
<li><a href="#lb">Configuring Nginx as Load Balancer in Dockerized .NET app</a></li>
<li><a href="#ssl">Adding SSL certificate to Nginx</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</nav>
</div>
<h2 id="install">Installing Nginx in Windows</h2>
<p>I will be installing Nginx on my Windows 11 OS. Go to <a href="https://nginx.org/en/download.html" target="_blank">Nginx download page</a>, to download a stable version for Windows. The zip file will get downloaded to your pc.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/nginx-download.png" alt="Nginx Download" title="Nginx Download" class="img-fluid"></p>
<p>Extract the zip file to your <u>C:/Program Files</u> location.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/nginx-program-files.png" alt="Nginx Install" title="Nginx Install" class="img-fluid"></p>
<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. <i>Configuring Nginx as Reverse Proxy and Load Balancer for Dockerized ASP.NET Core apps</i></li>
<li>8. <a target="_blank" href="https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/" rel="noopener">Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</a></li>
</ul>
</div>
<p>Next, go to the directory of Nginx with Command Prompt, the code is:</p>



<pre class="wp-block-code"><code>cd C:\Program Files\nginx-1.28.1</code></pre>



<p>Then start the Nginx with:</p>



<pre class="wp-block-code"><code>start nginx</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/start-nginx.png" alt="Start Nginx Command" title="Start Nginx Command" class="img-fluid"></p>
<div class="starBlock">Note that you run Nginx commands from Command Prompt window only and not from Powershell because Nginx is not recognized in powershell.</div>
<p>Now, open <u>http://localhost</u> on your browser to see Nginx default page.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/nginx-default-page.png" alt="Nginx Default Page" title="Nginx Default Page" class="img-fluid"></p>
<h3 id="solution">Solution to Nginx starting problem in Windows</h3>
<p>Firewalls and Antivirus sometimes block Nginx from starting, so you need to unblock it and put an exception for Nginx on firewall rules. To do this click the &#8220;nginx.exe&#8221; file on the directory and select &#8220;Run Anyway&#8221;.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/unblock-firewall-nginx.png" alt="Nginx Unblock Firewall" title="Nginx Unblock Firewall" class="img-fluid"></p>
<p>You should also free port 80 on your machine. Open Command Prompt on Administrator privileges and run the following command:</p>



<pre class="wp-block-code"><code>net stop http</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/net-stop-http.png" alt="net stop http" title="net stop http" class="img-fluid"></p>
<p>You can also try to use a different port for Nginx. Open &#8220;nginx.conf&#8221; file inside the &#8220;conf&#8221; folder and update the server portion where port 80 is given to another port like 9999 &#8211; <code>listen       9999</code>. See below:</p>



<pre class="wp-block-code"><code>server {
    listen       9999;
    server_name  localhost;
    ....
 }</code></pre>



<p>Again run the <code>start nginx</code> command then open &#8220;http://localhost:9999&#8221; and this time you will be able to see Nginx default page.</p>
<div class="starBlock">If you want to learn about CICD with Docker then read – <a href="https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/" target="_blank">Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</a></div>
<p>Some Nginx command you should know are:</p>



<pre class="wp-block-code"><code>start nginx // starts nginx
nginx -s stop	// stops nginx
nginx -s quit	// exit nginx
nginx -s reload  // reload nginx
nginx -V         // check nginx version
nginx -t         // check configuration

taskkill /f /IM nginx.exe  // checks nginx tasks in windows

tasklist /fi "imagename eq nginx.exe"  // kills nginx processes in windows</code></pre>



<h2 id="rp">Nginx as Reverse Proxy in Dockerized ASP.NET Core app</h2>
<p>ASP.NET Core apps are hosted by Kestrel which is Microsoft&#8217;s open-source, cross-platform, high-performance web server for ASP.NET Core. Kestrel handles HTTP requests and passes them to the apps middleware pipeline. Here Nginx sits in-front of Kestrel Web Server and intercepting client requests and forwards them the Kestrel web server which is hosting the app. In the same way the response from Kestrel first goes to Nginx and then Nginx sends it to the client. This is known as Reverse Proxy setup of Nginx.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/nginx-reverse-proxy.png" alt="Nginx Reverse Proxy" title="Nginx Reverse Proxy" class="img-fluid"></p>
<p>Let us implement Nginx reverse proxy on a Dockerized ASP.NET Core app. So create a new ASP.NET Core MVC app then to it add Container Compose Support.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/Container-compose-Support.png" alt="Container Compose Support" class="img-fluid" title="Container Compose Support"></p>
<p>It&#8217;s <u>docker-compose.yml</u> file initial code is.</p>



<pre class="wp-block-code"><code>services:
  nginxaspnetcore:
    image: ${DOCKER_REGISTRY-}nginxaspnetcore
    build:
      context: .
      dockerfile: NginxASPNETCore/Dockerfile</code></pre>



<p>And <u>docker-compose.override.yml</u> file initial code is.</p>



<pre class="wp-block-code"><code>services:
  nginxaspnetcore:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=8080
      - ASPNETCORE_HTTPS_PORTS=8081
    ports:
      - "8080"
      - "8081"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro</code></pre>



<p>We now add the ports and environment variables to the <u>docker-compose.yml</u> file as shown below.</p>



<pre class="wp-block-code"><code>services:
  nginxaspnetcore:
    image: ${DOCKER_REGISTRY-}nginxaspnetcore
    build:
      context: .
      dockerfile: NginxASPNETCore/Dockerfile
    ports:
        - "4001:8080"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=http://+:8080</code></pre>



<p>Build this app from <code>docker-compose build</code> then run the container with <code>docker-compose up</code> commands.</p>
<p>The above configuration will enable us to access the app from <u>http://localhost:4001</u> on the browser. See the below image of this app.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/dockerized-dotnet.png" alt="Dockerized .NET app" title="Dockerized .NET app" class="img-fluid"></p>
<p>The app is hosted on docker compose over kestrel web server. It&#8217;s time we configure Nginx as a revers proxy. Edit the <u>nginx.conf</u> file located inside the &#8220;conf&#8221; folder to include the following code.</p>



<pre class="wp-block-code"><code>worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       localhost:80;
        server_name  localhost;

        location / {
            proxy pass http://127.0.0.1:4001/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr; 
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}</code></pre>



<p>The main changes done to the nginx.conf file is that it now listen to <u>localhost:80</u> and server_name is defined <u>localhost</u>.</p>



<pre class="wp-block-code"><code>listen       localhost:80;
server_name  localhost;</code></pre>



<p>Then inside the location directive we provided the route of our Dockerized .NET app which is <u>http://127.0.0.1:4001/</u>.</p>



<pre class="wp-block-code"><code>proxy pass http://127.0.0.1:4001/;</code></pre>



<p>Then we configured Nginx to tell backend application which hostname was originally requested. This is done by:</p>



<pre class="wp-block-code"><code>proxy_set_header Host $host;</code></pre>



<p>After that with proxy_set_header Nginx is directed to modify or add HTTP headers sent to the Kestrel server, allowing us to pass crucial information like the original client&#8217;s IP (X-Real-IP, X-Forwarded-For).</p>



<pre class="wp-block-code"><code>proxy_set_header X-Real-IP $remote_addr; </code></pre>



<p>Reload Nginx with <code>nginx -s reload</code> and then open <u>http://localhost</u> on the browser which will open the ASP.NET Core app. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/nginx-reverse-proxy-aspnet-core.png" alt="Nginx Reverse Proxy ASP.NET Core" title="Nginx Reverse Proxy ASP.NET Core" class="img-fluid"></p>
<h2 id="lb">Configuring Nginx as Load Balancer in Dockerized .NET app</h2>
<p>We now Configuring Nginx as Load Balancer in Dockerized .NET app. Nginx will distribute incoming network traffic across multiple backend servers to ensure high availability, optimal performance, and scalability of the web app.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/nginx-as-load-balancer.png" alt="Nginx as Load Balancer" title="Nginx as Load Balancer" class="img-fluid"></p>
<p>We will first host our app from multiple docker containers which will replicate the scenario of hosting an app from multi servers.</p>
<p>Change the apps <u>docker-compose.yml</u> file as shown below.</p>



<pre class="wp-block-code"><code>services:
  nginxaspnetcore1:
    image: ${DOCKER_REGISTRY-}nginxaspnetcore
    build:
      context: .
      dockerfile: NginxASPNETCore/Dockerfile
    ports:
        - "4001:8080"
    environment:
      - appname=First
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=http://+:8080

  nginxaspnetcore2:
    image: ${DOCKER_REGISTRY-}nginxaspnetcore
    build:
      context: .
      dockerfile: NginxASPNETCore/Dockerfile
    ports:
        - "4002:8080"
    environment:
      - appname=Second
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=http://+:8080

  nginxaspnetcore3:
    image: ${DOCKER_REGISTRY-}nginxaspnetcore
    build:
      context: .
      dockerfile: NginxASPNETCore/Dockerfile
    ports:
        - "4003:8080"
    environment:
      - appname=Third
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=http://+:8080</code></pre>



<p>Here we added 3 services &#8211; nginxaspnetcore1, nginxaspnetcore2, nginxaspnetcore3 for the 3 docker containers that will host the app. On the ports section we exposed the ports &#8211; 4001, 4002 and 4003. We also added an environment variable &#8220;appname&#8221; to find out which container served which request. You will see this thing later on.</p>
<p>We also have to add the 3 services on <u>docker-compose.override.yml</u> file as shown below.</p>



<pre class="wp-block-code"><code>services:
  nginxaspnetcore1:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=8080
      - ASPNETCORE_HTTPS_PORTS=8081
    ports:
      - "8080"
      - "8081"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro

  nginxaspnetcore2:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=8080
      - ASPNETCORE_HTTPS_PORTS=8081
    ports:
      - "8080"
      - "8081"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro

  nginxaspnetcore3:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_HTTP_PORTS=8080
      - ASPNETCORE_HTTPS_PORTS=8081
    ports:
      - "8080"
      - "8081"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro</code></pre>



<p>On the HomeController.cs file&#8217;s Index action method we grab the environment &#8220;appname&#8221; value and write it to the console.</p>



<pre class="wp-block-code"><code>public IActionResult Index()
{
    string app = Environment.GetEnvironmentVariable("appname");
    Console.WriteLine("Request served to - " + app);
    return View();
}</code></pre>



<p>Build this app from <code>docker-compose build</code> then run the container with <code>docker-compose up</code> commands.</p>
<p>The above configuration will enable us to access the app from 3 urls &#8211; <u>http://localhost:4001</u>, <u>http://localhost:4002</u>, <u>http://localhost:4003</u> on the browser. See the below image of this app.</p>
<div class="starBlock">Related Docker Tutorial &#8211; <a href="https://www.yogihosting.com/docker-volumes-aspnet-core/">Docker Volumes on ASP.NET Core App</a></div>
<p>Next, we will configure Nginx as a Load Balancer. This will make any client request to be directed to any of the 3 Urls given above. In the nginx.conf file we add upstream directive and added add the 3 url of the app to which NGINX, acting as a load balancer, forwards client requests. The least_conn directive in NGINX specifies a dynamic load balancing method that directs new incoming requests to the upstream server which currently has the least number of active connections. By default requests to the application servers are distributed in a round-robin fashion.</p>



<pre class="wp-block-code"><code>upstream mycluster {
    least_conn;
    server 127.0.0.1:4001;
    server 127.0.0.1:4002;
    server 127.0.0.1:4003;
}</code></pre>



<p>Call this upstream from proxy_pass as <u>proxy_pass http://mycluster;</u>. The updated code is given below.</p>



<pre class="wp-block-code"><code>worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    upstream mycluster {
        least_conn;
        server 127.0.0.1:4001;
        server 127.0.0.1:4002;
        server 127.0.0.1:4003;
    }

    server {
        listen       localhost:80;
        server_name  localhost;

        location / {
            proxy_pass http://mycluster;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr; 
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}</code></pre>



<p>Reload Nginx with <code>nginx -s reload</code> and then open <u>http://localhost</u> on the browser which will open the ASP.NET Core app from any of the 3 containers which is idle (having least connection). Open the logs of the container to find out which one served the request. You will see the environment variable value &#8211; Request served to &#8211; First. See the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/nginx-load-balancer.png" alt="Nginx Load Balancer" title="Nginx Load Balancer" class="img-fluid"></p>
<div class="noteBlock">On local development the first container will always serve the request since the app is not doing any real task. To see different containers handling the request simply remove least_conn so that nginx will serve request in round robin fashion.</div>
<h2 id="ssl">Adding SSL certificate to Nginx</h2>
<p>We will now add SSL certificate to Nginx. To create SSL we will uses OpenSSL. The best way to install OpenSSL in windows is by insalling <a href="https://git-scm.com/install/windows" target="_blank">Git</a>. Git will install OpenSSL automatically on your PC.</p>
<div class="starBlock">Most advanced Docker tutorial packed with lots of knowledge &#8211; <a href="https://www.yogihosting.com/docker-aspnet-core-sql-server-crud/">CRUD Operations in ASP.NET Core and SQL Server with Docker</a></div>
<p>In windows Start Menu search for Git Bash which opens a terminal window. Here we write OpenSSL commands.</p>
<p>Create a new folder called &#8220;SSL&#8221; on the project directory. On Git Bash Navigate to this SSL folder with the CD command as shown below. Note that the folder location will be different in your case.</p>



<pre class="wp-block-code"><code>cd D:/Yogesh/SEO/Yogihosting/new/'Docker Nginx'/NginxASPNETCore/NginxASPNETCore/SSL</code></pre>



<p>Next run the following command which will generate the SSL.</p>



<pre class="wp-block-code"><code>openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx-selfsigned.key -out nginx-selfsigned.crt</code></pre>



<p>You will be asked to enter some details like your country, city, email, etc. I have shown this in the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/OpenSSL-windows.png" alt="OpenSSL Windows" title="OpenSSL Windows" class="img-fluid"></p>
<p>Two files will be created one is the SSL itself name &#8220;nginx-selfsigned.crt&#8221; and the other is it&#8217;s private key named &#8220;nginx-selfsigned.key&#8221;. We now have to add it to nginx.conf file.</p>
<p>Update the nginx.conf file to include the SSL as shown below.</p>



<pre class="wp-block-code"><code>worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    upstream mycluster {
        least_conn;
        server 127.0.0.1:4001;
        server 127.0.0.1:4002;
        server 127.0.0.1:4003;
    }

    server {
        listen localhost:80;
        server_name localhost;

         location / {
            return 301 https://$host$request_uri;
        }
    }

    server {
        listen       443 ssl;
        server_name  localhost;

        ssl_certificate "D:/Yogesh/SEO/Yogihosting/new/Docker Nginx/NginxASPNETCore/NginxASPNETCore/SSL/nginx-selfsigned.crt";
        ssl_certificate_key "D:/Yogesh/SEO/Yogihosting/new/Docker Nginx/NginxASPNETCore/NginxASPNETCore/SSL/nginx-selfsigned.key";

        location / {
            proxy_pass http://mycluster;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr; 
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}</code></pre>



<p>In the above configuration we added a new server block which listens on 443 port and then provided ssl file and path with ssl_certificate &#038; ssl_certificate_key. The server block for 80 port is now trimmed, it only redirects to the 443 port.</p>
<p>Open the website <u>http://locahost</u> which will redirect you to <u>https://locahost</u>. You will be seeing the warning message since it is Self Signed. Check the below image.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/aspnet-core-openssl.png" alt="OpenSSL ASP.NET Core" title="OpenSSL ASP.NET Core" class="img-fluid"></p>
<p>You can now download the source codes of this tutorial:</p>
<p><a class="testLink" href="https://www.yogihosting.com/wp-content/themes/yogi-yogihosting/download/docker/NginxASPNETCore.zip" target="_blank" rel="nofollow noopener noreferrer">Download</a></p>
<div id="conclusion" class="note">Conclusion</div>
<p>In this tutorial we leaned to configure Nginx as both reverse proxy and load balancer for .NET Dockerized apps. We also generated self signed certificates with openssl on windows and configured nginx to use this ssl. Check the below links for more such tutorials.</p>
<p>The post <a href="https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/">Configuring Nginx as Reverse Proxy and Load Balancer for Dockerized ASP.NET Core apps</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Entity Framework Core Testing Procedure</title>
		<link>https://www.yogihosting.com/testing-entity-framework-core/</link>
					<comments>https://www.yogihosting.com/testing-entity-framework-core/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Mon, 05 Jan 2026 16:45:56 +0000</pubDate>
				<category><![CDATA[EF Core]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22456</guid>

					<description><![CDATA[<p>Testing is an important part of any application since it tells the developers that their application works correctly. In this tutorial we are going to learn the different techniques to employ in order to test Entity Framework Core codes. These techniques are broadly divided into testing with production database or without the production database. These [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/testing-entity-framework-core/">Entity Framework Core Testing Procedure</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Testing is an important part of any application since it tells the developers that their application works correctly. In this tutorial we are going to learn the different techniques to employ in order to test Entity Framework Core codes. These techniques are broadly divided into testing with production database or without the production database. These are:</p>
<ul>
<li>Testing against the production database.</li> 
<li>Testing without the production database &#8211; here we use SQLite (in-memory mode) as a database fake, EF Core in-memory provider as a database fake, Mock DbSet and use repository layer to exclude EF Core entirely from testing and to fully mock the repository.</li>
</ul>



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



<div class="starBlock">The source code for this repository can be downloaded from my <a href="https://github.com/yogyogi/Entity-Framework-Core-Testing" rel="nofollow" target="_blank">GitHub repository</a>.</div>
<div id="contentTable">
<div class="title"><p class="left">Page Contents</p><p class="right"><span title="click to toggle"></span></p></div>
<nav>
<ul>
<li><a href="#app">Example ASP.NET Core app for Testing Entity Framework Core codes</a></li>
<li><a href="#pdb">Testing against the Production Database</a>
<ul>
<li><a href="#seed">Creating, seeding and managing Database</a></li>
<li><a href="#test">Creating Test Methods</a></li>
</ul>
</li>
<li><a href="#without">Testing without the Production Database</a>
<ul>
<li><a href="#repository">Repository Pattern</a></li>
<li><a href="#sql">SQLite in-memory</a></li>
<li><a href="#inmemory">In-memory provider</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</nav>
</div>
<h2 id="app">Example ASP.NET Core app for Testing Entity Framework Core codes</h2>
<p>To understand the process of testing Entity Framework Core codes we will create an example app in ASP.NET Core MVC which depicts the working of a news agency. Start by creating a new ASP.NET Core MVC app and name it <span class="term">EFCoreTesting</span>.</p>
<p>To the app install the following NuGet packages.</p>
<div class="note">EF Core packages</div>
<ul>
<li>Microsoft.EntityFrameworkCore.SqlServer</li>
<li>Microsoft.EntityFrameworkCore.Design</li>
<li>Microsoft.EntityFrameworkCore.Tools</li>
</ul>
<div class="note">Database packages</div>
<ul>
<li>Microsoft.Data.Sqlite</li>
<li>Microsoft.EntityFrameworkCore.InMemory</li>
<li>Microsoft.EntityFrameworkCore.Sqlite</li>
</ul>
<div class="note">Testing packages</div>
<ul>
<li>xunit</li>
<li>xunit.runner.visualstudio</li>
<li>Microsoft.NET.Test.Sdk</li>
<li>Moq</li>
</ul>
<p>All these packages will be used during the course of this tutorial.</p>
<p>Next, add 2 Entity Classes, preferably inside the &#8220;Models&#8221; folder, which are shown below.</p>



<pre class="wp-block-code"><code>public class News
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
}

public class UrlResource
{
    public string Url { get; set; }
}</code></pre>



<p>Next, add Dbcontext, preferably inside the &#8220;Models&#8221; folder, which is given below.</p>



<pre class="wp-block-code"><code>public class NewsContext : DbContext
{
    private readonly Action&lt;NewsContext, ModelBuilder&gt; _modelCustomizer;

    #region Constructors
    public NewsContext()
    {
    }

    public NewsContext(DbContextOptions&lt;NewsContext&gt; options, Action&lt;NewsContext, ModelBuilder&gt; modelCustomizer = null)
        : base(options)
    {
        _modelCustomizer = modelCustomizer;
    }
    #endregion

    public DbSet&lt;News&gt; News =&gt; Set&lt;News&gt;();
    public DbSet&lt;UrlResource&gt; UrlResources =&gt; Set&lt;UrlResource&gt;();

    #region OnConfiguring
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
        }
    }
    #endregion

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity&lt;UrlResource&gt;().HasNoKey()
            .ToView("AllResources");

        if (_modelCustomizer is not null)
        {
            _modelCustomizer(this, modelBuilder);
        }
    }
}</code></pre>



<p>The app is ready so we move to the testing part.</p>
<h2 id="pdb">Testing against the Production Database</h2>
<p>The main hurdle with testing against the production database is to ensure proper test isolation, so that tests don&#8217;t interfere with each other. This will be discussed on the test cases. Note that we will be using xUnit testing package to write the tests.</p> 
<h3 id="seed">Creating, seeding and managing Database</h3>
<p>Note that we should ensure database is created and seeded exactly once. For this we use xUnit class fixture feature. This ensures that the fixture instance will be created before any of the tests have run, and once all the tests have finished, it will clean up the fixture object by calling Dispose, if present.</p>
<div class="starBlock">Want to debug EF Core then check this &#8211; <a href="https://www.yogihosting.com/events-diagnostic-listeners-entity-framework-core/">Entity Framework Core Events and Diagnostic Listeners</a></div>
<p>Define a <span class="term">TestDatabaseFixture.cs</span> class in your project with the following code. Here we defined the connection string to the database. When the fixture is instantiated, in the constructor it uses <span class="code">EnsureDeleted()</span> to drop the database and then EnsureCreated() to create it with your latest configuration. Once the database is created, it seeds it with some data for the tests to use them.</p>



<pre class="wp-block-code"><code>public class TestDatabaseFixture
{
    private const string ConnectionString = @"Server=(localdb)\mssqllocaldb;Database=NewsAgency;Trusted_Connection=True;ConnectRetryCount=0";

    private static readonly object _lock = new();
    private static bool _databaseInitialized;

    public TestDatabaseFixture()
    {
        lock (_lock)
        {
            if (!_databaseInitialized)
            {
                using (var context = CreateContext())
                {
                    context.Database.EnsureDeleted();
                    context.Database.EnsureCreated();

                    context.AddRange(
                        new News { Name = "Donald Trump wins 2025 USA Presidential Election", Url = "https://newsabc.com/usa-2025-donald-trump/" },
                        new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" });
                    context.SaveChanges();
                }

                _databaseInitialized = true;
            }
        }
    }

    public NewsContext CreateContext()
        =&gt; new NewsContext(
            new DbContextOptionsBuilder&lt;NewsContext&gt;()
                .UseSqlServer(ConnectionString)
                .Options);
}
</code></pre>



<p>There is a locking feature in the fixture&#8217;s creation logic above. This ensures that the database creation and seeding are done exactly once for all the tests.</p>
<div class="note">Example</div>
<p>In the controller there are method to get news, get all news, add a new and update news url. The controller code is given below.</p>



<pre class="wp-block-code"><code>&#91;ApiController]
&#91;Route("&#91;controller]")]
public class NewsController : ControllerBase
{
    private readonly NewsContext _context;

    public NewsController(NewsContext context)
        =&gt; _context = context;

    &#91;HttpGet]
    public async Task&lt;ActionResult&lt;News&gt;&gt; GetNews(string name)
    {
        var news = await _context.News.FirstOrDefaultAsync(b =&gt; b.Name == name);
        return news is null ? NotFound() : news;
    }

    &#91;HttpGet]
    &#91;Route("GetAllNews")]
    public IAsyncEnumerable&lt;News&gt; GetAllNews()
        =&gt; _context.News.OrderBy(b =&gt; b.Name).AsAsyncEnumerable();

    &#91;HttpPost]
    public async Task&lt;ActionResult&gt; AddNews(string name, string url)
    {
        _context.News.Add(new News { Name = name, Url = url });
        await _context.SaveChangesAsync();

        return Ok();
    }

    &#91;HttpPost]
    public async Task&lt;ActionResult&gt; UpdateNewsUrl(string name, string url)
    {
        // Note: it isn't usually necessary to start a transaction for updating. This is done here for illustration purposes only.
        await using var transaction = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable);

        var news = await _context.News.FirstOrDefaultAsync(b =&gt; b.Name == name);
        if (news is null)
        {
            return NotFound();
        }

        news.Url = url;
        await _context.SaveChangesAsync();

        await transaction.CommitAsync();
        return Ok();
    }
}</code></pre>



<p>We are now going to test these methods.</p>
<h3 id="test">Creating Test Methods</h3>
<p>In the app create a new folder called <span class="term">Tests</span>, in this folder we will create our test class files. So first add a new class file called <span class="term">NewsControllerTest.cs</span>, to this file add the following tests.</p>



<pre class="wp-block-code"><code>public class NewsControllerTest : IClassFixture&lt;TestDatabaseFixture&gt;
{
    public NewsControllerTest(TestDatabaseFixture fixture)
    =&gt; Fixture = fixture;

    public TestDatabaseFixture Fixture { get; }

    &#91;Fact]
    public async Task GetNews()
    {
        using var context = Fixture.CreateContext();
        var controller = new NewsController(context);

        var news = (await controller.GetNews("Elon Musk worth soared to $1 trillion")).Value;

        Assert.Equal("https://newsabc.com/elon-musk-trillionaire/", news.Url);
    }

    &#91;Fact]
    public async Task GetAllNews()
    {
        using var context = Fixture.CreateContext();
        var controller = new NewsController(context);

        var news = await controller.GetAllNews().ToListAsync();

        Assert.Collection(
            news,
            b =&gt; Assert.Equal("Donald Trump wins 2025 USA Presidential Election", b.Name),
            b =&gt; Assert.Equal("Elon Musk worth soared to $1 trillion", b.Name));
    }
    
    &#91;Fact]
    public async Task AddNews()
    {
        using var context = Fixture.CreateContext();
        context.Database.BeginTransaction();

        var controller = new NewsController(context);
        await controller.AddNews("Bitcoin set to reach $1 million", "https://newsabc.com/btc-one-million/");

        context.ChangeTracker.Clear();

        var news = await context.News.SingleAsync(b =&gt; b.Name == "Bitcoin set to reach $1 million");
        Assert.Equal("https://newsabc.com/btc-one-million/", news.Url);
    }
}</code></pre>



<p><u>Explanation: </u>To use the fixture in a test class, we implement <span class="term">IClassFixture&lt;TestDatabaseFixture></span>, then xUnit will inject it into the constructor.</p>



<pre class="wp-block-code"><code>public class NewsControllerTest : IClassFixture&lt;TestDatabaseFixture&gt;
{
    public NewsControllerTest(TestDatabaseFixture fixture)
    =&gt; Fixture = fixture;

    public TestDatabaseFixture Fixture { get; }
}</code></pre>



<p>We now have Fixture property to create the context.</p>



<pre class="wp-block-code"><code>using var context = Fixture.CreateContext();</code></pre>



<p>See the test methods <span class="term">GetNews()</span> and <span class="term">GetAllNews()</span> where we are using the fixture property to create the DbContext and then testing the returned records data with <span class="term">Assert.Equal()</span> method.</p>



<pre class="wp-block-code"><code>&#91;Fact]
public async Task GetNews()
{
    using var context = Fixture.CreateContext();
    var controller = new NewsController(context);

    var news = (await controller.GetNews("Elon Musk worth soared to $1 trillion")).Value;

    Assert.Equal("https://newsabc.com/elon-musk-trillionaire/", news.Url);
}

&#91;Fact]
public async Task GetAllNews()
{
    using var context = Fixture.CreateContext();
    var controller = new NewsController(context);

    var news = await controller.GetAllNews().ToListAsync();

    Assert.Collection(
        news,
        b =&gt; Assert.Equal("Donald Trump wins 2025 USA Presidential Election", b.Name),
        b =&gt; Assert.Equal("Elon Musk worth soared to $1 trillion", b.Name));
}</code></pre>



<p>Note that the above 2 test methods are just a read only process where no database updating is required. We also have a test method where database is inserted with new news. Tests where data modification happens tends to written differently since they may interfere with one another. These test needs to be done in isolation. See the <span class="term">AddNews()</span> test method where we used transaction.</p>



<pre class="wp-block-code"><code>&#91;Fact]
public async Task AddNews()
{
    using var context = Fixture.CreateContext();
    context.Database.BeginTransaction();

    var controller = new NewsController(context);
    await controller.AddNews("Bitcoin set to reach $1 million", "https://newsabc.com/btc-one-million/");

    context.ChangeTracker.Clear();

    var news = await context.News.SingleAsync(b =&gt; b.Name == "Bitcoin set to reach $1 million");
    Assert.Equal("https://newsabc.com/btc-one-million/", news.Url);
}</code></pre>



<p>The transaction is never committed and it is rolled back at the end of the test. So no database changes happens and this make the test don&#8217;t interfere with other tests. When the database updates are made the change tracker is cleared with <span class="term">context.ChangeTracker.Clear()</span>, to make sure we actually load the news from the database.</p>
<div class="noteBlock">Must read tutorial on EF Core &#8211; <a href="https://www.yogihosting.com/interceptors-entity-framework-core/" target="_blank">Entity Framework Core Interceptors</a></div>
<p>The <span class="code">UpdateNewsUrl()</span> method of the News Controller which modify data and also explicitly manage transactions. As the databases do not typically support nested transactions, it isn&#8217;t possible to use transactions for isolation as we did in the above test above.</p>



<pre class="wp-block-code"><code>&#91;HttpPost]
public async Task&lt;ActionResult&gt; UpdateNewsUrl(string name, string url)
{
    // Note: it isn't usually necessary to start a transaction for updating. This is done here for illustration purposes only.
    await using var transaction = await _context.Database.BeginTransactionAsync(IsolationLevel.Serializable);

    var news = await _context.News.FirstOrDefaultAsync(b =&gt; b.Name == name);
    if (news is null)
    {
        return NotFound();
    }

    news.Url = url;
    await _context.SaveChangesAsync();

    await transaction.CommitAsync();
    return Ok();
}</code></pre>



<p>In such tests we must clean up the database to its original state after each test, and parallelization must be disabled so that these tests don&#8217;t interfere with each other. Here we  define another fixture with separate database, to make sure it don&#8217;t interfere with the other tests.</p>
<p>So, to the <u>Tests</u> folder add a new class called <span class="term">TransactionalTestDatabaseFixture.cs</span> with the following code.</p>



<pre class="wp-block-code"><code>public class TransactionalTestDatabaseFixture
{
    private const string ConnectionString = @"Server=(localdb)\mssqllocaldb;Database=NewsAgency;Trusted_Connection=True;ConnectRetryCount=0";

    public NewsContext CreateContext()
        =&gt; new NewsContext(
            new DbContextOptionsBuilder&lt;NewsContext&gt;()
                .UseSqlServer(ConnectionString)
                .Options);

    public TransactionalTestDatabaseFixture()
    {
        using var context = CreateContext();
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        Cleanup();
    }

    public void Cleanup()
    {
        using var context = CreateContext();

        context.News.RemoveRange(context.News);

        context.AddRange(
            new News { Name = "Donald Trump wins 2025 USA Presidential Election", Url = "https://newsabc.com/usa-2025-donald-trump/" },
            new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" });
        context.SaveChanges();
    }
}

&#91;CollectionDefinition("TransactionalTests")]
public class TransactionalTestsCollection : ICollectionFixture&lt;TransactionalTestDatabaseFixture&gt;
{
}</code></pre>



<p>In this fixture the Cleanup method is called after every test to ensure that the database is reset to its starting state. We want to share this fixture between multiple classes, so we don&#8217;t need these classes  run in parallel, to avoid any interference. To do that, we will use this as an xUnit collection fixture rather than as a class fixture. We define a test collection, which references the fixture and will be used by all transactional test classes which require it. The code is:</p>



<pre class="wp-block-code"><code>&#91;CollectionDefinition("TransactionalTests")]
public class TransactionalTestsCollection : ICollectionFixture&lt;TransactionalTestDatabaseFixture&gt;
{
}</code></pre>



<p>Now to the &#8220;Tests&#8221; folder add a new class called <span class="term">TransactionalNewsControllerTest.cs</span> where we write the test method.</p>



<pre class="wp-block-code"><code>&#91;Collection("TransactionalTests")]
public class TransactionalNewsControllerTest: IDisposable
{
    public TransactionalNewsControllerTest(TransactionalTestDatabaseFixture fixture)
    =&gt; Fixture = fixture;

    public TransactionalTestDatabaseFixture Fixture { get; }

    &#91;Fact]
    public async Task UpdateNewsUrl()
    {
        using (var context = Fixture.CreateContext())
        {
            var controller = new NewsController(context);
            await controller.UpdateNewsUrl("Elon Musk worth soared to $1 trillion", "https://newsabc.com/elon-musk-trillionaire-news-updated/");
        }

        using (var context = Fixture.CreateContext())
        {
            var news = await context.News.SingleAsync(b =&gt; b.Name == "Elon Musk worth soared to $1 trillion");
            Assert.Equal("https://newsabc.com/elon-musk-trillionaire-news-updated/", news.Url);
        }
    }
    
    public void Dispose()
        =&gt; Fixture.Cleanup();
}</code></pre>



<p>Note that xUnit doesn&#8217;t parallelize tests within the same class. If we want to share this fixture between multiple classes, we must make sure these classes don&#8217;t run in parallel, to avoid any interference. To do that, we will use this as an xUnit collection fixture (notice the attribute [Collection(&#8220;TransactionalTests&#8221;)]
) rather than as a class fixture. To do this we define a test collection, which references our fixture and will be used by all transactional test classes which require it.</p>
<p>Now xUnit instantiates the collection fixture once, there is no need for us to use locking around database creation and seeding as before.</p>
<h2 id="without">Testing without the Production Database</h2>
<p>When you have decided to perform testing without using the Production Database then you can find the following approaches:</p>
<ol>
<li>Repository Pattern</li>
<li>In-Memory Database</li>
</ol>
<h3 id="repository">Repository Pattern</h3>
<p>In the Repository Pattern we put all the EF Core LINQ queries to a separate layer which is known as Repository. This Repository later is used for creating simulated objects (mocks) that mimic real dependencies (like databases, APIs, or other classes) to test a component in isolation.</p>
<p>Start by adding an interface called <u>INewsRepository.cs</u> to the Models folder with the following code.</p>



<pre class="wp-block-code"><code>public interface INewsRepository
{
    Task&lt;News&gt; GetNewsByNameAsync(string name);

    IAsyncEnumerable&lt;News&gt; GetAllNewsAsync();

    void AddNews(News news);

    Task SaveChangesAsync();
}</code></pre>



<p>Next, implement the interface in a class called <u>NewsRepository.cs</u>. The code is given below.</p>



<pre class="wp-block-code"><code>public class NewsRepository : INewsRepository
{
    private readonly NewsContext _context;

    public NewsRepository(NewsContext context)
        =&gt; _context = context;

    public async Task&lt;News&gt; GetNewsByNameAsync(string name)
        =&gt; await _context.News.FirstOrDefaultAsync(b =&gt; b.Name == name);

    public IAsyncEnumerable&lt;News&gt; GetAllNewsAsync()
        =&gt; _context.News.AsAsyncEnumerable();

    public void AddNews(News news)
        =&gt; _context.Add(news);

    public async Task SaveChangesAsync()
        =&gt; await _context.SaveChangesAsync();
}</code></pre>



<p>The repository has methods to execute LINQ queries on the database which allows us to easily mock these repository methods.</p>
<p>We need to register the repository as a service in dependency injection by adding the following to the application&#8217;s Program class.</p>



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



<p>With this setup the controllers get injected with the repository service instead of the EF Core context. Now add a new controller called <span class="term">NewsControllerWithRepository.cs</span> where methods interacts with the database using repository. The code is given below.</p>



<pre class="wp-block-code"><code>&#91;ApiController]
&#91;Route("&#91;controller]")]
public class NewsControllerWithRepository : ControllerBase
{
    private readonly INewsRepository _repository;

    public NewsControllerWithRepository(INewsRepository repository)
        =&gt; _repository = repository;

    &#91;HttpGet]
    public async Task&lt;News&gt; GetNews(string name)
        =&gt; await _repository.GetNewsByNameAsync(name);

    &#91;HttpGet]
    public IAsyncEnumerable&lt;News&gt; GetAllNews()
        =&gt; _repository.GetAllNewsAsync();

    &#91;HttpPost]
    public async Task AddNews(string name, string url)
    {
        _repository.AddNews(new News { Name = name, Url = url });
        await _repository.SaveChangesAsync();
    }

    &#91;HttpPost]
    public async Task&lt;ActionResult&gt; UpdateNewsUrl(string name, string url)
    {
        var news = await _repository.GetNewsByNameAsync(name);
        if (news is null)
        {
            return NotFound();
        }

        news.Url = url;
        await _repository.SaveChangesAsync();

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



<p>Now the contact with the data access layer i.e. EF Core is via the repository layer which acts as a mediator between application code and actual database queries. Tests can now be written simply by stubbing out the repository, or by mocking it with your favorite <span class="term">MOQ</span> library. Recall the MOQ package was already installed to the app.</p>
<div class="noteBlock">Complete knowledge of change tracking &#8211; <a href="https://www.yogihosting.com/change-tracking-entity-framework-core/" target="_blank">Entity Framework Core Change Tracking</a></div>
<p>Add <span class="term">RepositoryNewsControllerTest.cs</span> to the &#8220;Tests&#8221; folder. It&#8217;s code is given below.</p>



<pre class="wp-block-code"><code>public class RepositoryNewsControllerTest
{
    &#91;Fact]
    public async Task GetNews()
    {
        // Arrange
        var repositoryMock = new Mock&lt;INewsRepository&gt;();
        repositoryMock
            .Setup(r =&gt; r.GetNewsByNameAsync("Elon Musk worth soared to $1 trillion"))
            .Returns(Task.FromResult(new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" }));

        var controller = new NewsControllerWithRepository(repositoryMock.Object);

        // Act
        var news = await controller.GetNews("Elon Musk worth soared to $1 trillion");

        // Assert
        repositoryMock.Verify(r =&gt; r.GetNewsByNameAsync("Elon Musk worth soared to $1 trillion"));
        Assert.Equal("https://newsabc.com/elon-musk-trillionaire/", news.Url);
    }

    &#91;Fact]
    public async Task GetAllNews()
    {
        // Arrange
        var repositoryMock = new Mock&lt;INewsRepository&gt;();
        repositoryMock
            .Setup(r =&gt; r.GetAllNewsAsync())
            .Returns(new&#91;]
            {
            new News { Name = "Donald Trump wins 2025 USA Presidential Election", Url = "https://newsabc.com/usa-2025-donald-trump/" },
            new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" }
            }.ToAsyncEnumerable());

        var controller = new NewsControllerWithRepository(repositoryMock.Object);

        // Act
        var news = await controller.GetAllNews().ToListAsync();

        // Assert
        repositoryMock.Verify(r =&gt; r.GetAllNewsAsync());
        Assert.Equal("https://newsabc.com/usa-2025-donald-trump/", news&#91;0].Url);
        Assert.Equal("https://newsabc.com/elon-musk-trillionaire/", news&#91;1].Url);
    }

    &#91;Fact]
    public async Task AddNews()
    {
        // Arrange
        var repositoryMock = new Mock&lt;INewsRepository&gt;();
        var controller = new NewsControllerWithRepository(repositoryMock.Object);

        // Act
        await controller.AddNews("Elon Musk worth soared to $1 trillion", "https://newsabc.com/elon-musk-trillionaire/");

        // Assert
        repositoryMock.Verify(r =&gt; r.AddNews(It.IsAny&lt;News&gt;()));
        repositoryMock.Verify(r =&gt; r.SaveChangesAsync());
    }

    &#91;Fact]
    public async Task UpdateNewsUrl()
    {
        var news = new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" };

        // Arrange
        var repositoryMock = new Mock&lt;INewsRepository&gt;();
        repositoryMock
            .Setup(r =&gt; r.GetNewsByNameAsync("Elon Musk worth soared to $1 trillion"))
            .Returns(Task.FromResult(news));

        var controller = new NewsControllerWithRepository(repositoryMock.Object);

        // Act
        await controller.UpdateNewsUrl("Elon Musk worth soared to $1 trillion", "https://newsabc.com/elon-musk-trillionaire-news-updated/");

        // Assert
        repositoryMock.Verify(r =&gt; r.GetNewsByNameAsync("Elon Musk worth soared to $1 trillion"));
        repositoryMock.Verify(r =&gt; r.SaveChangesAsync());
        Assert.Equal("https://newsabc.com/elon-musk-trillionaire-news-updated/", news.Url);
    }
}</code></pre>



<p>In the above MOQ code we first create a MOQ object.</p>



<pre class="wp-block-code"><code>var repositoryMock = new Mock&lt;INewsRepository&gt;();</code></pre>



<p>Then do some setup to it which tells what to return when a method of the repository is called.</p>



<pre class="wp-block-code"><code>repositoryMock
    .Setup(r =&gt; r.GetNewsByNameAsync("Elon Musk worth soared to $1 trillion"))
    .Returns(Task.FromResult(new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" }));</code></pre>



<p>Finally, after calling the repository method we do the assertions.</p>



<pre class="wp-block-code"><code>repositoryMock.Verify(r =&gt; r.GetNewsByNameAsync("Elon Musk worth soared to $1 trillion"));
Assert.Equal("https://newsabc.com/elon-musk-trillionaire/", news.Url);</code></pre>



<h3 id="sql">SQLite in-memory</h3>
<p>Instead of your production database we can use SQLite in-memory database for testing. Since it is based on memory so there are no SQLite files involved.</p>
<p>By default, in SQLite in-memory, a new database is created whenever a connection is opened, and that it&#8217;s deleted when that connection is closed. This would lead to resetting the database every time and is a problem so we will open a connection before passing it to EF Core, and arrange for it to be closed only when the test completes. This method will solve the problem.</p>
<p>Add <span class="term">SqliteInMemoryNewsControllerTest.cs</span> to the &#8220;Tests&#8221; folder. It&#8217;s code is given below.</p>



<pre class="wp-block-code"><code>public class SqliteInMemoryNewsControllerTest : IDisposable
{
    private readonly DbConnection _connection;
    private readonly DbContextOptions&lt;NewsContext&gt; _contextOptions;

    public SqliteInMemoryNewsControllerTest()
    {
        // Create and open a connection. This creates the SQLite in-memory database, which will persist until the connection is closed
        // at the end of the test (see Dispose below).
        _connection = new SqliteConnection("Filename=:memory:");
        _connection.Open();

        // These options will be used by the context instances in this test suite, including the connection opened above.
        _contextOptions = new DbContextOptionsBuilder&lt;NewsContext&gt;()
            .UseSqlite(_connection)
            .Options;

        // Create the schema and seed some data
        using var context = new NewsContext(_contextOptions);

        if (context.Database.EnsureCreated())
        {
            using var viewCommand = context.Database.GetDbConnection().CreateCommand();
            viewCommand.CommandText = @"
CREATE VIEW AllResources AS
SELECT Url
FROM News;";
            viewCommand.ExecuteNonQuery();
        }

        context.AddRange(
            new News { Name = "Donald Trump wins 2025 USA Presidential Election", Url = "https://newsabc.com/usa-2025-donald-trump/" },
            new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" });
        context.SaveChanges();
    }

    NewsContext CreateContext() =&gt; new NewsContext(_contextOptions);

    public void Dispose() =&gt; _connection.Dispose();

    &#91;Fact]
    public async Task GetNews()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        var news = (await controller.GetNews("Elon Musk worth soared to $1 trillion")).Value;

        Assert.Equal("https://newsabc.com/elon-musk-trillionaire/", news.Url);
    }

    &#91;Fact]
    public async Task GetAllNews()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        var news = await controller.GetAllNews().ToListAsync();

        Assert.Collection(
            news,
            b =&gt; Assert.Equal("Donald Trump wins 2025 USA Presidential Election", b.Name),
            b =&gt; Assert.Equal("Elon Musk worth soared to $1 trillion", b.Name));
    }

    &#91;Fact]
    public async Task AddNews()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        await controller.AddNews("Bitcoin set to reach $1 million", "https://newsabc.com/btc-one-million/");

        var news = await context.News.SingleAsync(b =&gt; b.Name == "Bitcoin set to reach $1 million");
        Assert.Equal("https://newsabc.com/btc-one-million/", news.Url);
    }

    &#91;Fact]
    public async Task UpdateNewsUrl()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        await controller.UpdateNewsUrl("Elon Musk worth soared to $1 trillion", "https://newsabc.com/elon-musk-trillionaire-news-updated/");

        var news = await context.News.SingleAsync(b =&gt; b.Name == "Elon Musk worth soared to $1 trillion");
        Assert.Equal("https://newsabc.com/elon-musk-trillionaire-news-updated/", news.Url);
    }
}</code></pre>



<p>Tests call <span class="code">CreateContext()</span> method which returns a context for SQLite In-Memory database and having a clean database with the seeded data.</p>
<h3 id="inmemory">In-memory provider</h3>
<p>In the In-memory provider the test class constructor sets up and seeds a new in-memory database before each test. To the &#8220;Tests&#8221; folder add the <u>InMemoryNewsControllerTest.cs</u> file with the following code.</p>



<pre class="wp-block-code"><code>public class InMemoryNewsControllerTest
{
    private readonly DbContextOptions&lt;NewsContext&gt; _contextOptions;

    public InMemoryNewsControllerTest()
    {
        _contextOptions = new DbContextOptionsBuilder&lt;NewsContext&gt;()
            .UseInMemoryDatabase("NewsControllerTest")
            .ConfigureWarnings(b =&gt; b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
            .Options;

        using var context = new NewsContext(_contextOptions);

        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        context.AddRange(
            new News { Name = "Donald Trump wins 2025 USA Presidential Election", Url = "https://newsabc.com/usa-2025-donald-trump/" },
            new News { Name = "Elon Musk worth soared to $1 trillion", Url = "https://newsabc.com/elon-musk-trillionaire/" });

        context.SaveChanges();
    }

    &#91;Fact]
    public async Task GetNews()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        var news = (await controller.GetNews("Elon Musk worth soared to $1 trillion")).Value;

        Assert.Equal("https://newsabc.com/elon-musk-trillionaire/", news.Url);
    }

    &#91;Fact]
    public async Task GetAllNews()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        var news = await controller.GetAllNews().ToListAsync();

        Assert.Collection(
            news,
            b =&gt; Assert.Equal("Donald Trump wins 2025 USA Presidential Election", b.Name),
            b =&gt; Assert.Equal("Elon Musk worth soared to $1 trillion", b.Name));
    }

    &#91;Fact]
    public async Task AddNews()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        await controller.AddNews("Bitcoin set to reach $1 million", "https://newsabc.com/btc-one-million/");

        var news = await context.News.SingleAsync(b =&gt; b.Name == "Bitcoin set to reach $1 million");
        Assert.Equal("https://newsabc.com/btc-one-million/", news.Url);
    }

    &#91;Fact]
    public async Task UpdateNewsUrl()
    {
        using var context = CreateContext();
        var controller = new NewsController(context);

        await controller.UpdateNewsUrl("Elon Musk worth soared to $1 trillion", "https://newsabc.com/elon-musk-trillionaire-news-updated/");

        var news = await context.News.SingleAsync(b =&gt; b.Name == "Elon Musk worth soared to $1 trillion");
        Assert.Equal("https://newsabc.com/elon-musk-trillionaire-news-updated/", news.Url);
    }

    NewsContext CreateContext() =&gt; new NewsContext(_contextOptions, (context, modelBuilder) =&gt;
    {
        modelBuilder.Entity&lt;UrlResource&gt;()
            .ToInMemoryQuery(() =&gt; context.News.Select(b =&gt; new UrlResource { Url = b.Url }));
    });
}</code></pre>



<div id="conclusion" class="note">Conclusion</div>
<p>In this tutorial we learned all about the Entity Framework Core testing procedures involving the production database and also when production database is not involved. We also learned to use Repository patterns for Mocking EF Core and SQLite and In-Memory methods to perform testing.</p>
<p>The post <a href="https://www.yogihosting.com/testing-entity-framework-core/">Entity Framework Core Testing Procedure</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/testing-entity-framework-core/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Docker Volumes on ASP.NET Core App</title>
		<link>https://www.yogihosting.com/docker-volumes-aspnet-core/</link>
					<comments>https://www.yogihosting.com/docker-volumes-aspnet-core/#respond</comments>
		
		<dc:creator><![CDATA[yogihosting]]></dc:creator>
		<pubDate>Mon, 29 Dec 2025 09:01:17 +0000</pubDate>
				<category><![CDATA[ASP.NET Core apps in Docker]]></category>
		<guid isPermaLink="false">https://www.yogihosting.com/?p=22465</guid>

					<description><![CDATA[<p>Docker Volumes are persistent data stores for containers created and managed by Docker. Volumes are stored within a directory on the host machine and not inside the container. When we mount the volume into a container, then this directory is what&#8217;s mounted on the container. If an app is storing some data on files inside [&#8230;]</p>
<p>The post <a href="https://www.yogihosting.com/docker-volumes-aspnet-core/">Docker Volumes on ASP.NET Core App</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Docker Volumes are persistent data stores for containers created and managed by Docker. Volumes are stored within a directory on the host machine and not inside the container. When we mount the volume into a container, then this directory is what&#8217;s mounted on the container. If an app is storing some data on files inside the container then in case of a container crash the data is bound to be lost. Volumes comes into this scenario since here we can save these files on the volumes, in case of container crash the volume is unaffected (since it is outside the container), so the files are not lost.</p>



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



<div id="contentTable">
<div class="title"><p class="left">Page Contents</p><p class="right"><span title="click to toggle"></span></p></div>
<nav>
<ul>
<li><a href="#app">ASP.NET Core MVC APP with Docker Volume</a>
<ul>
<li><a href="#host">Hosing the ASP.NET Core MVC app from Docker Container</a></li>
</ul>
</li>
<li><a href="#dv">Adding Docker Volumes in ASP.NET Core</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>
</nav>
</div>
<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. <i>Docker Volumes on ASP.NET Core App</i></li>
<li>7. <a target="_blank" href="https://www.yogihosting.com/nginx-reverse-proxy-load-balancer-docker-aspnet-core-app/" rel="noopener">Configuring Nginx as Reverse Proxy and Load Balancer for Dockerized ASP.NET Core apps</a></li>
<li>8. <a target="_blank" href="https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/" rel="noopener">Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</a></li>
</ul>
</div>
<p>In the below image we can understand the working of the volume. The app is hosted from a container with a volume mounted on it. The data of the app is stored on the volume and data persists in case of container crash.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/docker-volumes-working.png" class="img-fluid" alt="Docker Volume Working" title="Docker Volume Working"></p>
<h2 id="app">ASP.NET Core MVC APP with Docker Volume</h2>
<p>To understand the concept of docker volume we will create an ASP.NET Core app that displays a form to the users telling them &#8211; &#8220;Congrats, You are the lucky chosen one and has won iPhone. Fill your details to grab it&#8221;. When users fill their details then it get saved in a text file. The form is shown below.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/lucky-winner-form.jpg" class="img-fluid" alt="Docker Volume Example" title="Docker Volume Example"></p>
<p>The goal is to make this text file persistent in nature i.e. it should not be deleted in case of a container crash.</p>
<p>The <span class="term">Index.cshtml</span> razor view code where the form is created is:</p>



<pre class="wp-block-code"><code>@{
    ViewData&#91;"Title"] = "Home Page";
}
@{
    if (ViewBag.Submitted == "Yes")
    {
        &lt;h1 class="display-4"&gt;We will contact you shortly and will send iPhone to your address&lt;/h1&gt;
    }
    else
    {
        string displayValue = ViewBag.Header == "Yes" ? "none" : "block";
        &lt;div class="text-center" style="display:@displayValue"&gt;
            &lt;h1 class="display-4"&gt;Congrats, You are the lucky chosen one and has won iPhone. Fill your details to grab it.&lt;/h1&gt;
            &lt;form method="post"&gt;
                &lt;div class="mb-3"&gt;
                    &lt;label class="form-label"&gt;Name&lt;/label&gt;
                    &lt;input class="form-control" name="name"&gt;
                &lt;/div&gt;

                &lt;div class="mb-3"&gt;
                    &lt;label class="form-label"&gt;Email address&lt;/label&gt;
                    &lt;input class="form-control" name="email"&gt;
                    &lt;div id="emailHelp" class="form-text"&gt;We'll never share your email with anyone else.&lt;/div&gt;
                &lt;/div&gt;
                &lt;button type="submit" class="btn btn-primary"&gt;Submit&lt;/button&gt;
            &lt;/form&gt;
        &lt;/div&gt;
    }
}</code></pre>



<div class="starBlock">SQL Server with Docker &#8211; <a href="https://www.yogihosting.com/docker-aspnet-core-sql-server-crud/">CRUD Operations in ASP.NET Core and SQL Server with Docker</a></div>
<p>The controllers action which saves the details in a text file is given below.</p>



<pre class="wp-block-code"><code>&#91;HttpPost]
public IActionResult Index(string name, string email)
{
    string docPath = "Emails";

    if (!Directory.Exists(docPath))
        Directory.CreateDirectory(docPath);

    using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "userdata.txt"), true))
    {
        outputFile.WriteLine(name + ", " + email);
    }
    ViewBag.Submitted = "Yes";

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



<p>We used StreamWriter class to store the user details (Name and Email) in comma separated manner in &#8220;Emails/userdata.txt&#8221; file.</p>



<pre class="wp-block-code"><code>Brock Lesner, brock.lesner@yahoo.com
Sachin Tendulkar, srt.cricket@gmail.com</code></pre>



<p>The <span class="term">User.cshtml</span> razor view will display all the users who submitted their data in a html table. It&#8217;s code is given below.</p>



<pre class="wp-block-code"><code>@{
    ViewData&#91;"Title"] = "Email";
}
&lt;h1&gt;Submitted Users&lt;/h1&gt;
&lt;div class="text-center"&gt;
    &lt;div class="table-responsive"&gt;
        &lt;table class="table table-striped table-bordered"&gt;
            &lt;thead&gt;
                &lt;tr class="table-primary"&gt;
                    &lt;th&gt;Name&lt;/th&gt;
                    &lt;th&gt;Email&lt;/th&gt;
                &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
                @Html.Raw(ViewBag.Users)
            &lt;/tbody&gt;
        &lt;/table&gt;
    &lt;/div&gt;
&lt;/div&gt;</code></pre>



<p>The respective action method code is:</p>



<pre class="wp-block-code"><code>public IActionResult User()
{
    string docPath = "Emails";
    string allData = string.Empty;
    try
    {
        using (StreamReader reader = new StreamReader(Path.Combine(docPath, "userdata.txt")))
        {
            while (reader.Peek() &gt;= 0)
            {
                string line = reader.ReadLine();
                allData += "&lt;tr&gt;&lt;td&gt;" + line.Split(',')&#91;0] + "&lt;/td&gt;&lt;td&gt;" + line.Split(',')&#91;1] + "&lt;/td&gt;&lt;/tr&gt;";
            }
        }
    }
    catch
    {
    }

    ViewBag.Users = allData;

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



<p>This is how the user data is displayed on the browser.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/submitted-user-text-file.png" class="img-fluid" alt="user data" title="User Data"></p>
<div class="starBlock">Nginx with Dockerized apps &#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>
<h3 id="host">Hosing the ASP.NET Core MVC app from Docker Container</h3>
<p>Right click the app name in the solution explorer then select <span class="term">Add ➤ Container Support</span> option.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2021/03/create-dockerfile-visual-studio.png" title="ASP.NET Core Docker Dockerfile" alt="ASP.NET Core Docker Dockerfile" class="img-fluid"></p>
<p>On the <u>Container Scaffolding Options</u> dialog keep all the settings as it is and click OK button.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2021/03/target-os-docker.png" title="Docker Target OS Docker" alt="docker target os docker" class="img-fluid"></p>
<p>In the command prompt, go to the directory of Dockerfile and run the docker build command which will build the image by the name of <span class="term">luckyapp:v1</span>.</p>



<pre class="wp-block-code"><code>docker build -t luckyapp:v1 -f Dockerfile .</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/build-docker-image.png" title="Docker Build Image" alt="Docker Build Image" class="img-fluid"></p>
<p>Next, run a docker container for this image. We named the container &#8220;Lucky&#8221; and exposed the port no 5002 of the host mapping to 8080 port of the container.</p>



<pre class="wp-block-code"><code>docker run --name Lucky -p 5002:8080 luckyapp:v1</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/run-docker-container.png" title="Docker Run Container" alt="Docker Run Container" class="img-fluid"></p>
<p>Open the apps url &#8211; <u>http://localhost:5002</u> on the browser. The app is opening from a docker container. Submit some user data on the form.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/submitted-user-text-file.png" class="img-fluid" alt="user data" title="User Data"></p>
<p>Lets open a bash session in the container with the following command. Note that &#8220;2106d72da5e2&#8221; is the container id.</p>



<pre class="wp-block-code"><code>docker exec -it -u root 2106d72da5e2 bash</code></pre>



<p>Inside the bash session we check what&#8217;s saved in the text file. Run the following commands one by one.</p>



<pre class="wp-block-code"><code>ls

cd Emails

ls

cat userdata.txt</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/docker-bash-session.png" class="img-fluid" alt="Docker Bash Session" title="Docker Bash Session"></p>
<p>We used cat command to view the files content which is shown as:</p>



<pre class="wp-block-code"><code>Brock Lesner, brock.lesner@yahoo.com
Sachin Tendulkar, srt.cricket@gmail.com</code></pre>



<p>Everything is working properly, but problems do not come knocking on the door. Say there is some server problem which leads to a container crash. To depict this scenario, We run the following commands one by one to stop the container, remove it from the machine then run a new container.</p>



<pre class="wp-block-code"><code>docker stop Lucky
docker rm Lucky
docker run --name Lucky -p 5002:8080 luckyapp:v1</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/docker-stop-rm-commands.png" class="img-fluid" alt="Docker stop rm Commands" title="Docker Stop RM Commands"></p>
<p>Let&#8217;s check the users page their is no data. The reason being the container crash also deletes the Emails folder and hence data is lost.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/nodata-docker.png" class="img-fluid" alt="No Data in Container" title="No Data in Container"></p>
<p>Lets check inside the container to confirm this thing. Do the same thing by opening the bash session (change the container id) and then ls to see all the files. You will not find any &#8220;Emails&#8221; folder. This confirms the folder is lost.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/docker-container-data-lost.png" class="img-fluid" alt="Data lost in Container" title="Data lost in Container"></p>
<h2 id="dv">Adding Docker Volumes in ASP.NET Core app</h2>
<p>The solution of the problem is Docker Volumes. We mount a folder on the host machine to the container through docker volumes and so even if the container crashes the folder will be safe  since it is outside the container. We remove the previous container by the below commands.</p>



<pre class="wp-block-code"><code>docker stop Lucky
docker rm Lucky</code></pre>



<p>Next, we spin a new container with docker volumes added by the <span class="term">-v</span> flag. After the -v flag, we first give the path of the folder on the host machine then &#8220;:&#8221; followed by the path inside the container. We will use this &#8211; <code>-v D:/Emails:/app/Emails</code>. So in our case the folder &#8220;Emails&#8221; on the &#8220;D&#8221; drive is the host path and &#8220;/app/Emails&#8221; is the path inside the container.</p> 
<div class="starBlock">If you want to learn about CICD with Docker then read &#8211; <a href="https://www.yogihosting.com/github-actions-cicd-docker-aspnet-core/" target="_blank">Deploy ASP.NET Core Dockerized app to Azure with GitHub Actions CI / CD</a></div>
<p>Recall the app was storing the data inside &#8220;Emails&#8221; folder located on the root folder. The &#8220;app&#8221; is the root folder containing the app&#8217;s dll so we gave the path &#8211; &#8220;/app/Emails&#8221;.</p>
The docker run command with volumes flag is given below.</p>



<pre class="wp-block-code"><code>docker run --name Lucky -p 5002:8080 -v D:/Emails:/app/Emails luckyapp:v1</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/docker-volume-command.png" class="img-fluid" alt="Docker Volume Command" title="Docker Volume Command"></p>
<p>Add some user data on the website. Next open the D:/Emails folder to find the userdata.txt file. Open this file to find all the records saved.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/docker-volume-mount-folder.png" class="img-fluid" alt="Docker Volume Mount folder" title="Mount folder"></p>
<p>Note that this time the records will be saved outside the container in this folder. This folder is what&#8217;s mounted inside the container. Open a bash session to see this folder present inside the container.</p>
<p>Lets make a directory called &#8220;xyz&#8221; inside the &#8220;Emails&#8221; folder on the container. We use mkdir command.</p>



<pre class="wp-block-code"><code>mkdir xyz</code></pre>



<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/mkdir-docker-container.png" class="img-fluid" alt="Docker mkdir" title="Docker mkdir">.</p>
<p>Check this directory inside the D:/Emails folder. You will find it there. This is because the directory is actually creating on the volumes and not inside the container. What you are seeing inside the container is just a reflection of the folder serving as volumes on the host machine.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/docker-volume-example.png" class="img-fluid" alt="Docker Volume example" title="Docker Volume example">.</p>
<p>Create a new text file called &#8220;new.txt&#8221; inside the D:/Emails folder and add a text &#8211; &#8220;Coding is simple&#8221;. Then save it.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/create-file-in-mounted-folder-docker.png" class="img-fluid" alt="Create file in Docker Volumes" title="Create file in Docker Volumes">.</p>
<p>On the bash session type &#8220;ls&#8221; command. You will find this file. Open this file with <code>cat new.txt</code> command. You will see the content &#8211; &#8220;Coding is simple&#8221;. Check the below screenshot where I have shown this thing.</p>
<p><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2025/12/cat-command-docker.png" class="img-fluid" alt="Cat Command" title="Cat Command"></p>
<p>You can now download the source codes of this tutorial:</p>
<p><a class="testLink" href="https://www.yogihosting.com/wp-content/themes/yogi-yogihosting/download/docker/LuckyWinner.zip" target="_blank" rel="nofollow noopener noreferrer">Download</a></p>
<div id="conclusion" class="note">Conclusion</div>
<p>With this we have come to the end of this docker volumes tutorial. I made my honest efforts to make you understand how volumes work in .NET apps. I hope you like it and understood everything about volumes. Do check my other docker tutorials made specifically for .NET developers.</p>
<p style="font-size:30px">We have launched <a href="https://www.udemy.com/course/docker-for-net-developers-complete-knowledge/" target="_blank">Docker for .NET Developers &#8211; Complete Knowledge</a> in Udemy, check it now &#8211; <a href="https://www.udemy.com/course/docker-for-net-developers-complete-knowledge/" target="_blank"><img decoding="async" src="https://www.yogihosting.com/wp-content/uploads/2026/01/udemy.png" alt="docker udemy course" title="Docker Udemy Course" class="img-fluid" /></a></p>
<p>The post <a href="https://www.yogihosting.com/docker-volumes-aspnet-core/">Docker Volumes on ASP.NET Core App</a> appeared first on <a href="https://www.yogihosting.com">YogiHosting</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.yogihosting.com/docker-volumes-aspnet-core/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
