<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ dotnet - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ dotnet - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 31 May 2026 22:32:15 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/dotnet/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Migrate from ASP.NET Framework to ASP.NET Core ]]>
                </title>
                <description>
                    <![CDATA[ Migrating to ASP.NET Core is a strategic upgrade that improves performance, scalability, and cross-platform support. Instead of a risky full rewrite, you can use an incremental approach, refactor for  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/migrate-from-asp-net-to-asp-net-core/</link>
                <guid isPermaLink="false">6a0d28828837277411cbeeb3</guid>
                
                    <category>
                        <![CDATA[ dotnet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ .NET ]]>
                    </category>
                
                    <category>
                        <![CDATA[ data migration ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gopinath Karunanithi ]]>
                </dc:creator>
                <pubDate>Wed, 20 May 2026 03:20:34 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/b403b6fe-7924-4c2c-8ae2-ce089c743c76.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Migrating to ASP.NET Core is a strategic upgrade that improves performance, scalability, and cross-platform support. Instead of a risky full rewrite, you can use an incremental approach, refactor for dependency injection, and prioritize testing. Start small, migrate APIs first, and gradually transition UI components to ensure a smooth and reliable modernization process.</p>
<p>In this article, you'll learn how to modernize legacy applications by migrating from ASP.NET Framework to ASP.NET Core. This guide covers architectural differences, migration strategies, step-by-step implementation, and best practices for building scalable, high-performance web applications.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-understanding-the-architectural-shift">Understanding the Architectural Shift</a></p>
</li>
<li><p><a href="#heading-key-challenges-in-migration">Key Challenges in Migration</a></p>
</li>
<li><p><a href="#heading-migration-strategies">Migration Strategies</a></p>
</li>
<li><p><a href="#heading-pre-migration-assessment">Pre-Migration Assessment</a></p>
</li>
<li><p><a href="#heading-step-by-step-migration-process">Step-by-Step Migration Process</a></p>
</li>
<li><p><a href="#heading-performance-and-scalability-gains">Performance and Scalability Gains</a></p>
</li>
<li><p><a href="#heading-deployment-modernization">Deployment Modernization</a></p>
</li>
<li><p><a href="#heading-common-pitfalls">Common Pitfalls</a></p>
</li>
<li><p><a href="#heading-real-world-use-cases">Real-World Use Cases</a></p>
</li>
<li><p><a href="#heading-best-practices-checklist">Best Practices Checklist</a></p>
</li>
<li><p><a href="#heading-when-you-should-not-migrate">When You Should NOT Migrate</a></p>
</li>
<li><p><a href="#heading-future-enhancements">Future Enhancements</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<p>Legacy systems built on the ASP.NET Framework have powered enterprise applications for over a decade. While stable and mature, these systems often struggle to meet modern requirements such as cross-platform deployment, cloud-native scalability, and high-performance workloads. As businesses evolve, the need to modernize these applications becomes unavoidable.</p>
<p>This is where ASP.NET Core comes in. Designed as a lightweight, modular, and high-performance Framework, ASP.NET Core enables developers to build scalable applications that run on Windows, Linux, and macOS.</p>
<p>In this article, we’ll explore a <strong>practical and technical approach</strong> to migrating legacy ASP.NET Framework applications to ASP.NET Core MVC. Instead of focusing on theory, the emphasis will be on <strong>architectural differences, migration strategies, and step-by-step execution</strong>.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before migrating to ASP.NET Core, developers should have a solid understanding of the MVC architecture, C#, and basic .NET development concepts. Familiarity with dependency injection, REST APIs, and Entity Framework will make the migration process significantly easier.</p>
<p>You should also have:</p>
<ul>
<li><p>.NET SDK installed (.NET 6 or later recommended)</p>
</li>
<li><p>Basic knowledge of CLI commands</p>
</li>
<li><p>Experience with NuGet package management</p>
</li>
<li><p>Understanding of IIS or web hosting environments</p>
</li>
<li><p>A version control system such as Git</p>
</li>
</ul>
<p>For enterprise projects, access to staging environments and automated testing pipelines is highly recommended before beginning migration.</p>
<h2 id="heading-understanding-the-architectural-shift"><strong>Understanding the Architectural Shift</strong></h2>
<p>Migrating to ASP.NET Core is not just a version upgrade—it’s a <strong>fundamental architectural shift.</strong></p>
<h3 id="heading-from-monolithic-to-modular"><strong>From Monolithic to Modular</strong></h3>
<p>ASP.NET relies heavily on the System.Web assembly, which tightly couples components such as HTTP handling, session state, and caching. In contrast, ASP.NET Core removes this dependency and introduces a modular middleware pipeline.</p>
<h3 id="heading-built-in-dependency-injection"><strong>Built-in Dependency Injection</strong></h3>
<p>Dependency Injection (DI) in ASP.NET required third-party libraries like Autofac or Ninject. ASP.NET Core includes DI natively, promoting better separation of concerns.</p>
<h3 id="heading-unified-runtime"><strong>Unified Runtime</strong></h3>
<p>ASP.NET Core runs on the modern .NET ecosystem (for example, <code>.NET 6+</code>), which unifies previously fragmented runtimes and improves performance.</p>
<h3 id="heading-configuration-overhaul"><strong>Configuration Overhaul</strong></h3>
<p>Configuration has moved from XML-based <code>web.config</code> files to flexible <code>JSON</code>-based systems like <code>appsettings.json</code>, supporting environment-specific configurations.</p>
<h2 id="heading-key-challenges-in-migration"><strong>Key Challenges in Migration</strong></h2>
<p>Before diving into the process, it’s important to understand the challenges:</p>
<ul>
<li><p><strong>Tightly coupled codebases</strong>: Legacy applications often mix business logic, UI, and data access.</p>
</li>
<li><p><strong>Unsupported APIs</strong>: Some APIs used in ASP.NET are not available in Core.</p>
</li>
<li><p><strong>Third-party dependencies</strong>: Older libraries may not support .NET Core.</p>
</li>
<li><p><strong>Authentication differences</strong>: Forms authentication and legacy identity systems require refactoring.</p>
</li>
<li><p><strong>Large monoliths</strong>: Breaking down large applications is time-consuming.</p>
</li>
</ul>
<p>Ignoring these challenges often leads to failed or incomplete migrations.</p>
<h2 id="heading-migration-strategies"><strong>Migration Strategies</strong></h2>
<p>Choosing the right migration approach is critical.</p>
<h3 id="heading-big-bang-migration"><strong>Big Bang Migration</strong></h3>
<p>This approach involves rewriting the entire application at once.</p>
<p><strong>Pros:</strong></p>
<ul>
<li><p>Clean architecture</p>
</li>
<li><p>No legacy baggage</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><p>High risk</p>
</li>
<li><p>Long timelines</p>
</li>
<li><p>Requires full regression testing</p>
</li>
</ul>
<p>This approach is rarely recommended for large systems.</p>
<h3 id="heading-incremental-migration-recommended"><strong>Incremental Migration (Recommended)</strong></h3>
<p>The <strong>Strangler Fig Pattern</strong> is commonly used here. New features are built in ASP.NET Core while gradually replacing legacy components.</p>
<p>The Strangler Fig Pattern is named after a vine that grows around a host tree and gradually replaces it over time. In software modernization, this pattern involves building new functionality alongside the existing application and routing specific requests to the new system as components are migrated. Instead of replacing the entire application at once, teams incrementally “strangle” the legacy system until the new <a href="http://ASP.NET">ASP.NET</a> Core application fully takes over.</p>
<p><strong>Benefits:</strong></p>
<ul>
<li><p>Reduced risk</p>
</li>
<li><p>Continuous delivery</p>
</li>
<li><p>Easier debugging</p>
</li>
</ul>
<h3 id="heading-hybrid-approach"><strong>Hybrid Approach</strong></h3>
<p>Run ASP.NET Framework and ASP.NET Core side by side. Certain modules (for example, <code>APIs</code>) can be migrated first while the UI remains unchanged.</p>
<p><strong>Pros:</strong></p>
<ul>
<li><p>Lower migration risk</p>
</li>
<li><p>Minimal disruption</p>
</li>
<li><p>Supports phased rollout</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><p>Increased operational complexity</p>
</li>
<li><p>Additional deployment overhead</p>
</li>
<li><p>Temporary architectural duplication</p>
</li>
</ul>
<p>This approach is especially useful for large enterprise systems where maintaining business continuity is more important than completing the migration quickly.</p>
<h2 id="heading-pre-migration-assessment"><strong>Pre-Migration Assessment</strong></h2>
<p>A successful migration starts with proper planning.</p>
<h3 id="heading-codebase-audit"><strong>Codebase Audit</strong></h3>
<ul>
<li><p>Identify reusable modules</p>
</li>
<li><p>Detect tightly coupled components</p>
</li>
<li><p>Analyze dependencies</p>
</li>
</ul>
<h3 id="heading-tooling-support"><strong>Tooling Support</strong></h3>
<p>Use tools like:</p>
<ul>
<li><p>.NET Portability Analyzer</p>
</li>
<li><p>API analyzers</p>
</li>
</ul>
<p>These help determine compatibility with ASP.NET Core.</p>
<h3 id="heading-define-scope"><strong>Define Scope</strong></h3>
<p>Decide what to migrate first:</p>
<ul>
<li><p>APIs</p>
</li>
<li><p>UI (MVC Views)</p>
</li>
<li><p>Background services</p>
</li>
</ul>
<h2 id="heading-step-by-step-migration-process"><strong>Step-by-Step Migration Process</strong></h2>
<h3 id="heading-step-1-upgrade-existing-application"><strong>Step 1: Upgrade Existing Application</strong></h3>
<p>Ensure your application is running on the latest version of ASP.NET Framework. This minimizes compatibility issues.</p>
<h3 id="heading-step-2-create-a-new-aspnet-core-mvc-project"><strong>Step 2: Create a New</strong> <strong>ASP.NET</strong> <strong>Core MVC Project</strong></h3>
<p>Use the CLI:</p>
<pre><code class="language-shell">dotnet new mvc -n ModernApp
</code></pre>
<p>This creates a clean project with a modern structure:</p>
<ul>
<li><p><code>Program.cs (entry point)</code></p>
</li>
<li><p><code>Controllers/</code></p>
</li>
<li><p><code>Views/</code></p>
</li>
<li><p><code>wwwroot/</code></p>
</li>
</ul>
<h3 id="heading-step-3-migrate-configuration"><strong>Step 3: Migrate Configuration</strong></h3>
<p>Replace <code>web.config</code> with <code>appsettings.json</code>.</p>
<p><strong>Old (</strong><code>web.config</code><strong>):</strong></p>
<pre><code class="language-xml">&lt;appSettings&gt;
  &lt;add key="ApiUrl" value="https://api.example.com" /&gt;
&lt;/appSettings&gt;
</code></pre>
<p><strong>New (</strong><code>appsettings.json</code><strong>):</strong></p>
<pre><code class="language-json">{
  "ApiSettings": {
    "BaseUrl": "https://api.example.com"
  }
}
</code></pre>
<p>Access in code:</p>
<pre><code class="language-csharp">public class HomeController : Controller
{
    private readonly IConfiguration _config;

    public HomeController(IConfiguration config)
    {
        _config = config;
    }

    public IActionResult Index()
    {
        var url = _config["ApiSettings:BaseUrl"];
        return View();
    }
}
</code></pre>
<h3 id="heading-step-4-replace-globalasax-with-middleware"><strong>Step 4: Replace</strong> <code>Global.asax</code> <strong>with</strong> <code>Middleware</code></h3>
<p>ASP.NET Core uses a middleware pipeline instead of lifecycle events.</p>
<pre><code class="language-csharp">var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseRouting();
app.UseAuthorization();

app.MapControllers();

app.Run();
</code></pre>
<p>This pipeline provides more control and flexibility compared to the old event-driven model.</p>
<h3 id="heading-step-5-migrate-controllers-and-views"><strong>Step 5: Migrate Controllers and Views</strong></h3>
<p>Controller logic remains similar, but return types change.</p>
<p><strong>ASP.NET</strong> <strong>Framework:</strong></p>
<pre><code class="language-csharp">public ActionResult Index()
{
    return View();
}
</code></pre>
<p><strong>ASP.NET</strong> <strong>Core:</strong></p>
<pre><code class="language-csharp">public IActionResult Index()
{
    return View();
}
</code></pre>
<p>Views (Razor) require minor updates, especially for tag helpers.</p>
<h3 id="heading-step-6-implement-dependency-injection"><strong>Step 6: Implement Dependency Injection</strong></h3>
<p>Replace tightly coupled services with DI.</p>
<pre><code class="language-csharp">public interface IProductService
{
    List&lt;string&gt; GetProducts();
}

public class ProductService : IProductService
{
    public List&lt;string&gt; GetProducts()
    {
        return new List&lt;string&gt; { "Laptop", "Phone" };
    }
}
</code></pre>
<p>Register service:</p>
<pre><code class="language-csharp">builder.Services.AddScoped&lt;IProductService, ProductService&gt;();
</code></pre>
<p>Use in controller:</p>
<pre><code class="language-csharp">public class ProductController : Controller
{
    private readonly IProductService _service;

    public ProductController(IProductService service)
    {
        _service = service;
    }

    public IActionResult Index()
    {
        var products = _service.GetProducts();
        return View(products);
    }
}
</code></pre>
<h3 id="heading-step-7-migrate-data-access-layer"><strong>Step 7: Migrate Data Access Layer</strong></h3>
<p>Most legacy apps use Entity Framework. In ASP.NET Core, you’ll use Entity Framework Core.</p>
<p><strong>DbContext Example:</strong></p>
<pre><code class="language-csharp">public class AppDbContext : DbContext
{
    public DbSet&lt;Product&gt; Products { get; set; }

    public AppDbContext(DbContextOptions&lt;AppDbContext&gt; options)
        : base(options) { }
}
</code></pre>
<p>Register in <code>Program.cs</code>:</p>
<pre><code class="language-csharp">builder.Services.AddDbContext&lt;AppDbContext&gt;(options =&gt;
    options.UseSqlServer("YourConnectionString"));
</code></pre>
<h3 id="heading-step-8-authentication-and-authorization"><strong>Step 8: Authentication and Authorization</strong></h3>
<p>ASP.NET Core provides flexible authentication mechanisms:</p>
<ul>
<li><p>Cookie-based authentication</p>
</li>
<li><p>JWT tokens</p>
</li>
<li><p>OAuth providers</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="language-csharp">builder.Services.AddAuthentication("CookieAuth")
    .AddCookie("CookieAuth", config =&gt;
    {
        config.LoginPath = "/Account/Login";
    });
</code></pre>
<h3 id="heading-step-9-testing-and-validation"><strong>Step 9: Testing and Validation</strong></h3>
<p>Testing becomes critical during migration:</p>
<ul>
<li><p>Unit testing (<code>xUnit</code>, <code>NUnit</code>)</p>
</li>
<li><p>Integration testing</p>
</li>
<li><p>Regression testing</p>
</li>
</ul>
<p>Ensure feature parity between old and new systems.</p>
<p>For example, after migrating a legacy ASP.NET Framework API to ASP.NET Core, you can validate behavior using integration tests to ensure responses match the original system.</p>
<p><strong>Example: Integration Test (xUnit)</strong></p>
<pre><code class="language-csharp">[Fact]
public async Task GetProducts_ShouldReturnSuccessStatus()
{
    // Arrange
    var client = _factory.CreateClient();

    // Act
    var response = await client.GetAsync("/api/products");

    // Assert
    response.EnsureSuccessStatusCode();
}
</code></pre>
<p>You can also compare legacy and migrated endpoints side by side during transition:</p>
<ul>
<li><p>/legacy/api/products → ASP.NET Framework</p>
</li>
<li><p>/api/products → ASP.NET Core</p>
</li>
</ul>
<p>Then run parallel requests and validate response consistency in structure, status codes, and data integrity. This helps ensure that migration does not introduce behavioral regressions before fully decommissioning the legacy system.</p>
<h2 id="heading-performance-and-scalability-gains"><strong>Performance and Scalability Gains</strong></h2>
<p>ASP.NET Core introduces significant improvements:</p>
<ul>
<li><p><strong>Kestrel Web Server</strong>: High-performance, cross-platform server</p>
</li>
<li><p><strong>Async-first design</strong>: Efficient request handling</p>
</li>
<li><p><strong>Lower memory usage</strong></p>
</li>
<li><p><strong>Better throughput under load</strong></p>
</li>
</ul>
<p>These improvements make it ideal for microservices and cloud deployments.</p>
<h2 id="heading-deployment-modernization"><strong>Deployment Modernization</strong></h2>
<p>Legacy applications were traditionally tightly coupled to IIS-based hosting environments, which limited flexibility in deployment and scaling. With ASP.NET Core, applications can now be deployed using modern, portable, and automated infrastructure approaches that better support cloud-native development and continuous delivery.</p>
<h3 id="heading-containerization"><strong>Containerization</strong></h3>
<p>Containerization allows applications to be packaged with all their dependencies into a single, portable unit that can run consistently across different environments. With tools like Docker, ASP.NET Core applications can be deployed reliably across development, staging, and production without environment-specific configuration issues. This approach also simplifies scaling and rollback strategies in distributed systems.</p>
<pre><code class="language-dockerfile">FROM mcr.microsoft.com/dotnet/aspnet:6.0
COPY . /app
WORKDIR /app
ENTRYPOINT ["dotnet", "ModernApp.dll"]
</code></pre>
<h3 id="heading-cicd-integration"><strong>CI/CD Integration</strong></h3>
<p>Continuous Integration and Continuous Deployment (CI/CD) pipelines automate the process of building, testing, and deploying applications. In migration scenarios, CI/CD becomes especially important because it ensures that both legacy and modernized components remain stable during incremental rollout. Platforms like GitHub Actions and Azure DevOps help teams validate changes quickly and reduce the risk of regression when transitioning from ASP.NET Framework to ASP.NET Core.</p>
<p>Using CI/CD also enables:</p>
<ul>
<li><p>Faster release cycles during phased migration</p>
</li>
<li><p>Automated testing of migrated modules</p>
</li>
<li><p>Safe rollback in case of deployment failures</p>
</li>
</ul>
<p>This ensures automated builds, tests, and deployments.</p>
<h2 id="heading-common-pitfalls"><strong>Common Pitfalls</strong></h2>
<h3 id="heading-underestimating-complexity"><strong>Underestimating Complexity</strong></h3>
<p>Migration is not just about converting code—it involves rethinking the entire architecture. Differences in middleware, configuration, and hosting models mean teams must redesign key components rather than simply port them.</p>
<h3 id="heading-ignoring-dependencies"><strong>Ignoring Dependencies</strong></h3>
<p>Many legacy applications rely on third-party libraries that may not support ASP.NET Core. Failing to assess compatibility early can lead to blockers, forcing last-minute rewrites or replacements.</p>
<h3 id="heading-skipping-incremental-approach"><strong>Skipping Incremental Approach</strong></h3>
<p>Attempting a full “Big Bang” migration increases risk and often results in delays or failures. An incremental strategy allows teams to validate changes gradually and maintain system stability throughout the transition.</p>
<h3 id="heading-poor-testing"><strong>Poor Testing</strong></h3>
<p>Insufficient testing can introduce critical bugs into production systems. Comprehensive unit, integration, and regression testing are essential to ensure the new application matches the behavior of the legacy system.</p>
<h2 id="heading-real-world-use-cases"><strong>Real-World Use Cases</strong></h2>
<h3 id="heading-enterprise-erp-modernization"><strong>Enterprise ERP Modernization</strong></h3>
<p>Large ERP systems built on ASP.NET often become difficult to scale and maintain due to tightly coupled architectures. Migrating these systems to ASP.NET Core allows organizations to modularize components, improve performance, and enable cloud deployment. This transition also makes it easier to introduce microservices and modern DevOps practices.</p>
<h3 id="heading-e-commerce-platform-scaling"><strong>E-commerce Platform Scaling</strong></h3>
<p>E-commerce applications require high availability and the ability to handle traffic spikes. By moving to ASP.NET Core, businesses can take advantage of asynchronous processing, improved request handling, and container-based scaling. This ensures faster page loads, better user experience, and the ability to handle peak demand efficiently.</p>
<h3 id="heading-api-first-backend-transformation"><strong>API-First Backend Transformation</strong></h3>
<p>Modern applications increasingly adopt an API-first approach, where the backend is designed as a set of independent services. ASP.NET Core simplifies building RESTful APIs with better routing, serialization, and performance. Organizations often migrate APIs first to establish a scalable backend, then gradually transition UI layers to align with the new architecture.</p>
<h2 id="heading-best-practices-checklist"><strong>Best Practices Checklist</strong></h2>
<h3 id="heading-start-with-a-small-module"><strong>Start with a Small Module</strong></h3>
<p>Instead of migrating the entire application at once, begin with a low-risk module such as a reporting feature or internal API. This helps teams understand the migration process, identify challenges early, and build confidence before scaling the effort across the system.</p>
<h3 id="heading-use-incremental-migration"><strong>Use Incremental Migration</strong></h3>
<p>An incremental approach, such as the Strangler Fig pattern, allows legacy and modern systems to coexist during the transition. This reduces downtime, minimizes risk, and ensures continuous delivery while gradually replacing legacy components with ASP.NET Core services.</p>
<h3 id="heading-refactor-for-dependency-injection-early"><strong>Refactor for Dependency Injection Early</strong></h3>
<p>Dependency injection is a core principle in ASP.NET Core, and adopting it early simplifies future development. Refactoring tightly coupled components into loosely coupled services improves testability, maintainability, and overall code quality during and after migration.</p>
<h3 id="heading-monitor-performance-continuously"><strong>Monitor Performance Continuously</strong></h3>
<p>Performance should be tracked throughout the migration process using logging and monitoring tools. Comparing metrics between the legacy system and the new implementation helps validate improvements and ensures that performance regressions are identified and addressed quickly.</p>
<h3 id="heading-maintain-backward-compatibility"><strong>Maintain Backward Compatibility</strong></h3>
<p>During migration, it is essential to ensure that existing clients and integrations continue to function correctly. Maintaining backward compatibility through versioned APIs or adapters allows a smooth transition without disrupting users or dependent systems.</p>
<h2 id="heading-when-you-should-not-migrate">When You Should NOT Migrate</h2>
<p>Migration is not always the right decision. If a legacy application is stable, rarely updated, and meeting business requirements, a full migration may not justify the cost and engineering effort. Some enterprise systems also depend heavily on Windows-specific technologies or third-party libraries that have no ASP.NET Core equivalents.</p>
<p>You should avoid immediate migration when:</p>
<ul>
<li><p>Business risk outweighs modernization benefits</p>
</li>
<li><p>The application is nearing retirement</p>
</li>
<li><p>Critical dependencies are unsupported in .NET Core</p>
</li>
<li><p>The team lacks resources for testing and refactoring</p>
</li>
<li><p>Downtime or instability could severely impact operations</p>
</li>
</ul>
<p>In such cases, maintaining the existing system while gradually modernizing specific modules may be a more practical strategy.</p>
<h2 id="heading-future-enhancements"><strong>Future Enhancements</strong></h2>
<p>As organizations continue modernizing applications with ASP.NET Core, future enhancements often focus on scalability, automation, and cloud-native development practices. Migrating to ASP.NET Core creates a foundation that makes it easier to adopt newer architectural patterns and infrastructure technologies.</p>
<h3 id="heading-microservices-adoption"><strong>Microservices Adoption</strong></h3>
<p>After migration, many organizations gradually break large monolithic systems into microservices. This improves scalability, fault isolation, and independent deployment of application components while enabling faster development cycles.</p>
<h3 id="heading-cloud-native-deployment"><strong>Cloud-Native Deployment</strong></h3>
<p>ASP.NET Core applications are well-suited for deployment on cloud platforms such as Microsoft Azure, Amazon Web Services (AWS), and Google Cloud. Future enhancements may include auto-scaling, serverless workloads, and managed container orchestration.</p>
<h3 id="heading-container-orchestration-with-kubernetes"><strong>Container Orchestration with Kubernetes</strong></h3>
<p>Containerized ASP.NET Core applications can be managed using Kubernetes for automated scaling, service discovery, and high availability. This is especially useful for enterprise-grade distributed systems.</p>
<h3 id="heading-advanced-observability-and-monitoring"><strong>Advanced Observability and Monitoring</strong></h3>
<p>Modern systems increasingly integrate observability platforms such as centralized logging, distributed tracing, and performance monitoring. Tools like Prometheus and Grafana help teams proactively detect issues and optimize performance.</p>
<h3 id="heading-api-gateway-and-service-mesh-integration"><strong>API Gateway and Service Mesh Integration</strong></h3>
<p>As applications evolve into distributed architectures, API gateways and service meshes become valuable for traffic management, authentication, and security. This enhances communication between services while improving resilience and governance.</p>
<h3 id="heading-ai-assisted-development-and-automation"><strong>AI-Assisted Development and Automation</strong></h3>
<p>Modern .NET ecosystems increasingly integrate AI-powered coding assistants, automated testing, and intelligent CI/CD pipelines. These tools can reduce development time, improve code quality, and simplify long-term maintenance of migrated applications.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Migrating from ASP.NET Framework to ASP.NET Core MVC is a strategic modernization effort, not just a technical upgrade. While the process involves challenges—ranging from architectural changes to dependency issues—the long-term benefits are substantial.</p>
<p>By adopting an incremental approach, leveraging modern tools, and focusing on clean architecture, organizations can successfully transition to a platform built for <strong>performance, scalability, and cloud-native development</strong>.</p>
<p>ASP.NET Core is not just the future of .NET—it’s the foundation for building resilient, modern applications in today’s distributed world.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Query Data in DynamoDB Using .Net ]]>
                </title>
                <description>
                    <![CDATA[ If you're coming to DynamoDB from a relational background, the first thing to understand is this: it's a completely different way of thinking. DynamoDB isn't a relational database, it's a NoSQL key-va ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-query-data-in-dynamodb-using-net/</link>
                <guid isPermaLink="false">69fa1ffca386d7f121b4955b</guid>
                
                    <category>
                        <![CDATA[ C# ]]>
                    </category>
                
                    <category>
                        <![CDATA[ DynamoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ dotnet ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Grant Riordan ]]>
                </dc:creator>
                <pubDate>Tue, 05 May 2026 16:51:08 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/66b52b176a1b17f6b28d9822/93c4db14-f870-47d6-99c9-e0816d8b628b.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you're coming to DynamoDB from a relational background, the first thing to understand is this: it's a completely different way of thinking.</p>
<p>DynamoDB isn't a relational database, it's a NoSQL key-value and document store. You don't write arbitrary queries against your data. Instead, you design your tables around the specific access patterns your application needs.</p>
<p>DynamoDB is driven by your queries, not your data.</p>
<p>There's no need for joins or heavy normalisation. To get the performance DynamoDB is built for, model your data so it can be retrieved efficiently using keys – partition keys and sort keys – rather than relying on table scans or complex query logic.</p>
<p>If you try to use DynamoDB like SQL, it will fight you — and you will lose.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>There are a few things you'll need and some general knowledge that you should have to follow along most effectively here:</p>
<p><strong>AWS</strong></p>
<ul>
<li><p>An active AWS account with permissions to create and modify DynamoDB</p>
</li>
<li><p>Basic familiarity with the AWS Console (navigating services, not deep expertise)</p>
</li>
</ul>
<p><strong>C# / .NET</strong></p>
<ul>
<li><p>Comfortable with C#</p>
</li>
<li><p>Dependency injection</p>
</li>
<li><p>NuGet package management — you'll need to install <code>AWSSDK.DynamoDBv2</code></p>
</li>
</ul>
<p><strong>Databases (Conceptual)</strong></p>
<ul>
<li>(Optional) A working understanding of relational databases / SQL is actually helpful here — the article explicitly addresses readers coming from that background and explains the mental shift required</li>
</ul>
<p><strong>What you don't need</strong></p>
<ul>
<li><p>Prior DynamoDB experience — the article covers core concepts from scratch</p>
</li>
<li><p>Deep AWS infrastructure knowledge — IAM, VPCs, and so on aren't covered</p>
</li>
</ul>
<p><strong>Optional but useful</strong></p>
<ul>
<li><p>AWS CLI installed locally if you want to follow the AWS CLI examples directly</p>
</li>
<li><p>Terraform experience if following the infrastructure-as-code example (Terraform section can be skipped without losing context)</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ul>
<li><p><a href="#heading-core-dynamodb-concepts">Core DynamoDB Concepts</a></p>
<ul>
<li><p><a href="#heading-partition-key">Partition Key</a></p>
</li>
<li><p><a href="#heading-sort-key-optional">Sort Key (Optional)</a></p>
</li>
<li><p><a href="#heading-global-secondary-index-gsi">Global Secondary Index (GSI)</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-synthetic-keys-the-old-way">Synthetic Keys — The Old Way</a></p>
</li>
<li><p><a href="#heading-multi-attribute-gsis-the-new-way">Multi-Attribute GSIs — The New Way</a></p>
<ul>
<li><p><a href="#heading-defining-a-multi-attribute-gsi">Defining a Multi-Attribute GSI</a></p>
</li>
<li><p><a href="#heading-query-rules-you-must-follow">Query Rules You Must Follow</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-c-sdk-options">C# SDK Options</a></p>
<ul>
<li><p><a href="#heading-low-level-client">Low-Level Client</a></p>
</li>
<li><p><a href="#heading-document-model">Document Model</a></p>
</li>
<li><p><a href="#heading-object-persistence-model">Object Persistence Model</a></p>
</li>
<li><p><a href="#heading-setting-up-the-context">Setting Up the Context</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-querying-a-multi-attribute-gsi-from-c">Querying a Multi-Attribute GSI from C</a></p>
</li>
<li><p><a href="#heading-query-vs-scan">Query vs Scan</a></p>
<ul>
<li><p><a href="#heading-query">Query</a></p>
</li>
<li><p><a href="#heading-scan">Scan</a></p>
</li>
<li><p><a href="#heading-when-is-a-scan-acceptable">When Is A Scan Acceptable?</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-filter-expressions">Filter Expressions</a></p>
</li>
<li><p><a href="#heading-paging-results-and-user-interfaces">Paging Results and User Interfaces</a></p>
<ul>
<li><p><a href="#heading-how-it-works-in-dynamodb">How It Works in DynamoDB</a></p>
</li>
<li><p><a href="#heading-how-this-works-in-the-dynamodb-c-sdk">How This Works In The DynamoDB C# SDK</a></p>
</li>
<li><p><a href="#heading-manual-pagination-the-right-approach-for-uis">Manual Pagination — The Right Approach For UIs</a></p>
</li>
<li><p><a href="#heading-what-about-go-to-to-page-7-navigation">What About "go to to page 7" Navigation?</a></p>
</li>
<li><p><a href="#heading-the-filterexpression-trap">The FilterExpression Trap</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-final-thoughts-amp-conclusion">Final Thoughts &amp; Conclusion</a></p>
</li>
</ul>
<h2 id="heading-core-dynamodb-concepts">Core DynamoDB Concepts</h2>
<p>DynamoDB is built around a few core concepts that directly influence how you query and structure your data.</p>
<p>Remember: DynamoDB is driven by how you retrieve your data, not by the shape of the data itself.</p>
<h3 id="heading-partition-key">Partition Key</h3>
<ul>
<li><p>Required for every query</p>
</li>
<li><p>Required to create a table – each table must have a partition key</p>
</li>
<li><p>Determines data distribution and how items are stored</p>
</li>
</ul>
<p>Data in DynamoDB is physically distributed across partitions. The partition key decides where a given item lives, which is why it's critical for both performance and scalability.</p>
<p>You can think of a partition key like a filing cabinet drawer label: it tells DynamoDB which drawer to open so it can go straight to your data without searching every drawer.</p>
<p>Partition keys are typically strings or numbers.</p>
<h3 id="heading-sort-key-optional">Sort Key (Optional)</h3>
<p>Also known as the <strong>Range Key</strong>.</p>
<ul>
<li>Enables range queries (<code>between</code>, <code>begins_with</code>, and so on)</li>
</ul>
<p>When you add a sort key, items with the same partition key are grouped together and ordered by the sort key. For string sort keys, ordering is lexicographical (dictionary order). For numeric sort keys, ordering is numeric (ascending).</p>
<p>This has an important effect when working with dates. If you store dates as strings in a non-ISO format (for example, <code>DD/MM/YYYY</code>), they won't sort in chronological order. For example, the resulting sorted items would look like this:</p>
<pre><code class="language-plaintext">01/01/2026
01/02/2026
01/03/2026
08/01/2026
15/02/2026
</code></pre>
<p>Here, all dates starting with <code>01</code> are grouped together, even though they span different months. This breaks range queries and ordering.</p>
<p>To avoid this, use either:</p>
<ul>
<li><p><strong>ISO 8601 format</strong> (<code>YYYY-MM-DD</code>), which sorts correctly as a string</p>
</li>
<li><p><strong>Unix timestamps</strong> (typically milliseconds since January 1st, 1970), which sort numerically and correctly</p>
</li>
</ul>
<p>Both approaches ensure your data is ordered correctly and can be queried efficiently. This is commonly used for timestamps, versioning, or logical groupings (for example, <code>ORDER#2024-01</code>).</p>
<h3 id="heading-global-secondary-index-gsi">Global Secondary Index (GSI)</h3>
<p>A Global Secondary Index (GSI) allows you to query your data using a different partition key and optional sort key than your base table. This is how you support additional access patterns without redesigning your primary key schema.</p>
<p>For example, if your base table is keyed by <code>UserId</code>, but you also need to query by <code>OrderId</code>, a GSI makes that possible.</p>
<h4 id="heading-projection-types">Projection Types</h4>
<p>A GSI doesn't have to include all attributes from the base table. When configuring your GSI you can choose:</p>
<ul>
<li><p><strong>ALL</strong> — all attributes are projected</p>
</li>
<li><p><strong>KEYS_ONLY</strong> — only index and primary keys</p>
</li>
<li><p><strong>INCLUDE</strong> — a subset of selected attributes</p>
</li>
</ul>
<p>Choosing the right projection helps reduce storage and read costs. For example, for an orders view you could project only <code>orderNumber</code>, <code>dateOrdered</code>, and <code>cost</code> rather than all other attributes which aren't needed.</p>
<h4 id="heading-important-considerations">Important Considerations</h4>
<p><strong>Additional cost:</strong> GSIs consume their own read/write capacity and storage in addition to your base table's costs. Using <code>ProjectionType = INCLUDE</code> or <code>KEYS_ONLY</code> instead of <code>ALL</code> reduces the storage cost of the GSI since less data is duplicated into the index, which can offset the additional read/write cost.</p>
<p><strong>Eventual consistency:</strong> In DynamoDB, when you write directly to the base table you have the option to perform a strongly consistent read immediately after — meaning you're guaranteed to get the latest data. GSIs don'r support this. GSI reads are always eventually consistent.</p>
<p>When a customer places an order, DynamoDB writes that order to your main Orders table. It then replicates that change to any GSIs asynchronously in the background. During that brief window (typically milliseconds), a query against a GSI may not return the newly written order yet.</p>
<p>A real-world example of where this can catch you out:</p>
<ol>
<li><p>Customer places an order which is written to the Orders table</p>
</li>
<li><p>User is redirected to the "Your Orders" page</p>
</li>
<li><p>"Your Orders" page queries a GSI for all orders by this customer</p>
</li>
<li><p>New order doesn't appear yet — GSI replication is still in progress</p>
</li>
<li><p>Customer refreshes the page 50ms later</p>
</li>
<li><p>Order now appears</p>
</li>
</ol>
<p>For most queries — browsing a product catalogue, viewing order history, filtering by status — this is completely acceptable and unnoticeable to the user. Where it matters is when your application writes a record and immediately queries a GSI for that same record. In this scenario you have a couple of options:</p>
<ul>
<li><p>Query the base table directly after a write using a strongly consistent read, rather than the GSI</p>
</li>
<li><p>Pass the order data directly to the UI from the write response, without a follow-up query at all — the cleanest solution in most cases</p>
</li>
</ul>
<p><strong>Write amplification:</strong> Every write to the base table may also write to one or more GSIs. GSIs are powerful, but they're not free. Overusing them is often a sign that your primary access patterns weren't well defined upfront.</p>
<p><strong>Note:</strong> DynamoDB allows a maximum of 20 GSIs per table by default, though this can be increased via an AWS service limit request.</p>
<h2 id="heading-synthetic-keys-the-old-way">Synthetic Keys — The Old Way</h2>
<p>Before we look at multi-attribute GSIs, it's worth understanding the pattern they replace — because you'll encounter it in existing DynamoDB codebases.</p>
<p>Imagine you want to query orders by both status and date — for example, all "pending" orders placed in the last 30 days. Previously, DynamoDB GSIs only supported a single attribute as the partition key and a single attribute as the sort key. To filter on multiple attributes you had to combine them into a single synthetic attribute:</p>
<pre><code class="language-csharp">[DynamoDBTable("Orders")]
public class OrderDto
{
    [DynamoDBHashKey("customerId")]
    public string CustomerId { get; set; }

    [DynamoDBRangeKey("createdAt")]
    public long CreatedAt { get; set; }

    [DynamoDBProperty("orderId")]
    public string OrderId { get; set; }

    [DynamoDBProperty("status")]
    public string Status { get; set; }

    // Synthetic key — manually constructed before saving
    [DynamoDBProperty("statusDate")]
    public string StatusDate { get; set; } // e.g. "PENDING#2025-11-01"
}
</code></pre>
<p>Constructing this value before saving the record:</p>
<pre><code class="language-csharp">order.StatusDate = $"{order.Status}#{order.CreatedAt:yyyy-MM-dd}";
</code></pre>
<p>Then create a GSI on <code>statusDate</code> as the partition key, allowing you to query:</p>
<pre><code class="language-csharp">var results = await _context.QueryAsync&lt;OrderDto&gt;(
    "PENDING#2025-11-01",
    config // IndexName = "statusDate-index"
).GetRemainingAsync();
</code></pre>
<p>This worked, but came with real downsides:</p>
<ul>
<li><p><strong>Brittle</strong> — every developer writing to the table must know about and correctly format the synthetic key</p>
</li>
<li><p><strong>Hard to query ranges</strong> — filtering all pending orders across a date range required careful <code>begins_with</code> or <code>between</code> conditions on a concatenated string</p>
</li>
<li><p><strong>Maintenance overhead</strong> — if status values change, every existing record needs updating</p>
</li>
<li><p><strong>Invisible in the schema</strong> — a new developer has no idea what <code>statusDate</code> means without documentation</p>
</li>
<li><p><strong>Backfilling</strong> — adding a new synthetic-key GSI to an existing table meant updating every existing record to populate the new attribute via a script re-processing the existing items.</p>
</li>
</ul>
<h2 id="heading-multi-attribute-gsis-the-new-way">Multi-Attribute GSIs — The New Way</h2>
<p>On November 19, 2025, AWS <a href="https://aws.amazon.com/about-aws/whats-new/2025/11/amazon-dynamodb-multi-attribute-composite-keys-global-secondary-indexes/">announced multi-attribute composite keys for GSIs</a>. You can now define a GSI partition key or sort key comprised of up to <strong>4 attributes each</strong> — <strong>8 attributes in total</strong> across the partition and sort key combined.</p>
<p>A few important things to note:</p>
<ul>
<li><p><strong>GSIs only:</strong> This applies to GSIs only — your base table primary key structure is unchanged, still a single partition key and an optional single sort key.</p>
</li>
<li><p><strong>DynamoDB handles composition internally:</strong> You don't concatenate values yourself. DynamoDB hashes the partition key attributes together for data distribution, and maintains hierarchical sort order across the sort key attributes.</p>
</li>
<li><p><strong>Strict query rules still apply:</strong> You must supply <strong>all</strong> partition key attributes with equality conditions when querying. Sort key attributes must be queried <strong>left-to-right</strong> in the order they were defined — you can't skip attributes.</p>
</li>
<li><p><strong>No backfilling required.</strong> When you add a multi-attribute GSI to an existing table, DynamoDB automatically indexes all existing items using their natural attributes.</p>
</li>
<li><p><strong>No additional cost</strong> beyond standard GSI pricing.</p>
</li>
</ul>
<p>The model stays clean — no synthetic attributes needed:</p>
<pre><code class="language-csharp">[DynamoDBTable("Orders")]
public class OrderDto
{
    [DynamoDBHashKey("customerId")]
    public string CustomerId { get; set; }

    [DynamoDBRangeKey("createdAt")]
    public long CreatedAt { get; set; }

    [DynamoDBProperty("orderId")]
    public string OrderId { get; set; }

    [DynamoDBProperty("status")]
    public string Status { get; set; }

    [DynamoDBProperty("total")]
    public decimal Total { get; set; }
}
</code></pre>
<h3 id="heading-defining-a-multi-attribute-gsi">Defining a Multi-Attribute GSI</h3>
<p>You can create the GSI via the AWS Console (select the attributes you want in order), via Terraform (requires AWS provider v6.29.0+), or via the AWS CLI.</p>
<p>The key concept to understand: you provide <strong>multiple</strong> <code>HASH</code> <strong>entries</strong> for the composite partition key and <strong>multiple</strong> <code>RANGE</code> <strong>entries</strong> for the composite sort key, in the exact order they should be evaluated. DynamoDB treats them internally as one composite partition key and one composite sort key.</p>
<p>Here's an AWS CLI example — a GSI on Orders with a composite partition key (<code>customerId</code> + <code>status</code>) and a single-attribute sort key (<code>createdAt</code>):</p>
<pre><code class="language-bash">aws dynamodb update-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=customerId,AttributeType=S \
    AttributeName=status,AttributeType=S \
    AttributeName=createdAt,AttributeType=N \
  --global-secondary-index-updates \
  "[{\"Create\":{
    \"IndexName\":\"customerStatus-createdAt-index\",
    \"KeySchema\":[
      {\"AttributeName\":\"customerId\",\"KeyType\":\"HASH\"},
      {\"AttributeName\":\"status\",\"KeyType\":\"HASH\"},
      {\"AttributeName\":\"createdAt\",\"KeyType\":\"RANGE\"}
    ],
    \"Projection\":{\"ProjectionType\":\"ALL\"}
  }}]"
</code></pre>
<p>The two <code>HASH</code> entries here are valid. They define a composite partition key of <code>(customerId, status)</code>. This is the syntax AWS introduced specifically for multi-attribute GSIs. It would have been rejected before November 2025.</p>
<p>Here's a Terraform example (AWS provider v6.29.0+):</p>
<pre><code class="language-hcl">global_secondary_index {
  name            = "customerStatus-createdAt-index"
  projection_type = "ALL"

  key_schema {
    attribute_name = "customerId"
    key_type       = "HASH"
  }

  key_schema {
    attribute_name = "status"
    key_type       = "HASH"
  }

  key_schema {
    attribute_name = "createdAt"
    key_type       = "RANGE"
  }
}
</code></pre>
<h3 id="heading-query-rules-you-must-follow">Query Rules You Must Follow</h3>
<p>The flexibility gain with multi-attribute GSIs is real, but the query constraints are not the same as SQL. Two rules matter most:</p>
<h4 id="heading-1-partition-key-attributes-must-all-be-supplied-with-equality-only">1. Partition key attributes must all be supplied, with equality only.</h4>
<p>For a partition key of <code>(customerId, status)</code>:</p>
<p><strong>Valid</strong>:</p>
<pre><code class="language-plaintext">customerId = 'C123' AND status = 'PENDING'
</code></pre>
<p><strong>Invalid</strong> — missing <code>status</code>:</p>
<pre><code class="language-plaintext">customerId = 'C123'
</code></pre>
<p><strong>Invalid</strong> — inequality on a partition key attribute:</p>
<pre><code class="language-plaintext">customerId = 'C123' AND status &gt; 'P'
</code></pre>
<h4 id="heading-2-sort-key-attributes-must-be-queried-left-to-right-with-inequality-only-as-the-final-condition">2. Sort key attributes must be queried left-to-right, with inequality only as the final condition.</h4>
<p>For a sort key of <code>(tournamentRound, rank, matchId)</code>:</p>
<p><strong>Valid</strong>:</p>
<pre><code class="language-plaintext">tournamentRound = 'SEMIFINALS'

tournamentRound = 'SEMIFINALS' AND rank = 'UPPER'

tournamentRound = 'SEMIFINALS' AND rank = 'UPPER' AND matchId = 'match-002'

tournamentRound = 'SEMIFINALS' AND rank = 'UPPER' AND matchId &gt; 'match-001'

tournamentRound BETWEEN 'QUARTERFINALS' AND 'SEMIFINALS'
</code></pre>
<p><strong>Invalid</strong> — skipping the first attribute:</p>
<pre><code class="language-plaintext">rank = 'UPPER'
</code></pre>
<p><strong>Invalid</strong> — leaving a gap (skipping <code>bracket</code>)</p>
<pre><code class="language-plaintext">tournamentRound = 'SEMIFINALS' AND matchId = 'match-002'
</code></pre>
<p><strong>Invalid</strong> — adding a condition after an inequality:</p>
<pre><code class="language-plaintext">tournamentRound &gt; 'QUARTERFINALS' AND rank = 'UPPER'
</code></pre>
<p><strong>Design tip:</strong> Order your sort key attributes from most general to most specific for example, <code>tournamentRound → rank → matchId</code>). This maximises query flexibility, since each left-to-right prefix becomes a valid query pattern.</p>
<h4 id="heading-going-deeper">Going deeper:</h4>
<p>AWS publishes a detailed design pattern guide with worked examples for time-series data, e-commerce orders, hierarchical organisation data, and multi-tenant SaaS platforms. The examples use the JavaScript SDK, but the schema design principles apply regardless of language. The article can be found <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.DesignPattern.MultiAttributeKeys.html">here</a>.</p>
<h2 id="heading-c-sdk-options">C# SDK Options</h2>
<p>When working with DynamoDB in C#, the <code>AWSSDK.DynamoDBv2</code> NuGet package gives you three different ways to interact with your tables, each with different levels of abstraction.</p>
<h3 id="heading-low-level-client">Low-Level Client</h3>
<pre><code class="language-csharp">var client = new AmazonDynamoDBClient();
</code></pre>
<p>The <code>AmazonDynamoDBClient</code> gives you full control over every aspect of your DynamoDB interactions. You construct requests manually, specifying every attribute, condition, and configuration explicitly.</p>
<pre><code class="language-csharp">var request = new QueryRequest
{
    TableName = "Orders",
    KeyConditionExpression = "customerId = :customerId",
    ExpressionAttributeValues = new Dictionary&lt;string, AttributeValue&gt;
    {
        { ":customerId", new AttributeValue { S = "customer-123" } }
    }
};

var response = await client.QueryAsync(request);
</code></pre>
<p>This is the most verbose approach, but nothing is hidden from you. You can see exactly what's being sent to DynamoDB, which makes it easier to debug, optimise, and understand exactly what Read Capacity Units (RCUs) you're consuming. It's also the most flexible — anything DynamoDB supports, you can do here.</p>
<p><strong>When to use it:</strong><br>When you need fine-grained control, are doing something complex, or want full visibility into your queries.</p>
<h3 id="heading-document-model">Document Model</h3>
<pre><code class="language-csharp">var client = new AmazonDynamoDBClient();
var table = Table.LoadTable(client, "Orders");
</code></pre>
<p>The Document Model sits a level above the low-level client. Rather than working with raw <code>AttributeValue</code> types, you work with <code>Document</code> objects which feel more like JSON — familiar to most .NET developers.</p>
<pre><code class="language-csharp">var filter = new QueryFilter("customerId", QueryOperator.Equal, "customer-123");
var search = table.Query(filter);
var documents = await search.GetRemainingAsync();

// access the data
foreach (var doc in documents)
{
    Console.WriteLine(doc["orderId"].AsString());
    Console.WriteLine(doc["total"].AsDecimal());
}
</code></pre>
<p>Less boilerplate than the low-level client, but you're still working with loosely typed <code>Document</code> objects rather than your own C# classes. There's no mapping to strongly typed models out of the box.</p>
<p><strong>When to use it:</strong><br>Useful for dynamic or loosely structured data where you don't want to define a fixed model, or for quick tooling and scripts.</p>
<h3 id="heading-object-persistence-model">Object Persistence Model</h3>
<p>The Object Persistence Model is the highest level of abstraction and the most natural fit for typical .NET development.</p>
<p>You decorate your C# classes with attributes, and the <code>DynamoDBContext</code> handles serialisation and deserialisation automatically — similar to an ORM like Entity Framework.</p>
<pre><code class="language-csharp">[DynamoDBTable("Orders")]
public class OrderRecord
{
    [DynamoDBHashKey("customerId")]
    public string CustomerId { get; set; }

    [DynamoDBRangeKey("createdAt")]
    public long CreatedAt { get; set; }

    [DynamoDBProperty("orderId")]
    public string OrderId { get; set; }

    [DynamoDBProperty("total")]
    public decimal Total { get; set; }

    [DynamoDBProperty("status")]
    public string Status { get; set; }
}
</code></pre>
<p>Querying feels clean and strongly typed:</p>
<pre><code class="language-csharp">var orders = await dbContext
    .QueryAsync&lt;OrderRecord&gt;("customer-123")
    .GetRemainingAsync();
</code></pre>
<p>The trade-off is that the abstraction hides some important details: you don't always see exactly what's being sent to DynamoDB under the hood, which can make debugging and performance optimisation harder.</p>
<h3 id="heading-setting-up-the-context">Setting Up the Context</h3>
<p>When creating the db context there are a couple of options:</p>
<h4 id="heading-option-1-using-dependency-injection">Option 1 — using Dependency Injection:</h4>
<pre><code class="language-csharp">// Program.cs
builder.Services.AddSingleton&lt;IAmazonDynamoDB, AmazonDynamoDBClient&gt;();

builder.Services.AddSingleton&lt;IDynamoDBContext&gt;(sp =&gt;
{
    var client = sp.GetRequiredService&lt;IAmazonDynamoDB&gt;();
    return new DynamoDBContext(client);
});

// Then in repository / service, inject IDynamoDBContext
public class OrderRepository
{
    private readonly IDynamoDBContext _context;

    public OrderRepository(IDynamoDBContext context)
    {
        _context = context;
    }
}
</code></pre>
<h4 id="heading-option-2-register-amazondynamodbclient-only-and-instantiate-the-context-per-operation">Option 2 — register <code>AmazonDynamoDBClient</code> only, and instantiate the context per operation:</h4>
<pre><code class="language-csharp">// Program.cs
builder.Services.AddSingleton&lt;IAmazonDynamoDB, AmazonDynamoDBClient&gt;();
</code></pre>
<p>Then:</p>
<pre><code class="language-csharp">public class OrderRepository
{
    private readonly IAmazonDynamoDB _client;

    public OrderRepository(IAmazonDynamoDB client)
    {
        _client = client;
    }

    public async Task&lt;List&lt;OrderDto&gt;&gt; GetOrdersAsync(string customerId)
    {
        var context = new DynamoDBContext(_client); // lightweight to instantiate
        return await context.QueryAsync&lt;OrderDto&gt;(customerId).GetRemainingAsync();
    }
}
</code></pre>
<p><strong>Which is better?</strong> Option 1 is cleaner and more testable — you can mock <code>IDynamoDBContext</code> in unit tests easily. Option 2 is also valid since <code>DynamoDBContext</code> is lightweight to instantiate, but you lose the ability to mock it cleanly.</p>
<p><strong>When to use Object Persistence:</strong> the recommended approach for most .NET applications. Clean, strongly typed, and fits naturally into existing C# codebases.</p>
<h2 id="heading-querying-a-multi-attribute-gsi-from-c">Querying a Multi-Attribute GSI From C#</h2>
<p>At the time of writing, the <code>DynamoDBContext.QueryAsync&lt;T&gt;</code> convenience overloads don't support multi-attribute GSI key conditions directly — you need to use the low-level client (<code>IAmazonDynamoDB</code>) and pass a <code>KeyConditionExpression</code>. The good news is the deserialisation back to your typed model is still straightforward.</p>
<p>Here's a query against a GSI with a composite partition key of <code>(customerId, status)</code> and a sort key of <code>createdAt</code>, returning all pending orders for a customer since a given date:</p>
<pre><code class="language-csharp">public async Task&lt;List&lt;OrderDto&gt;&gt; GetOrdersByStatusSinceAsync(
    string customerId,
    string status,
    long fromDate)
{
    var request = new QueryRequest
    {
        TableName = "Orders",
        IndexName = "customerStatus-createdAt-index",
        KeyConditionExpression =
            "customerId = :customerId " +
            "AND #status = :status " +           // #status because 'status' is a reserved word
            "AND createdAt &gt; :fromDate",
        ExpressionAttributeNames = new Dictionary&lt;string, string&gt;
        {
            { "#status", "status" }
        },
        ExpressionAttributeValues = new Dictionary&lt;string, AttributeValue&gt;
        {
            { ":customerId", new AttributeValue { S = customerId } },
            { ":status",     new AttributeValue { S = status } },
            { ":fromDate",   new AttributeValue { N = fromDate.ToString() } }
        },
        ScanIndexForward = false // reverse sort key order — newest first, since sort key is a timestamp
    };

    var response = await _client.QueryAsync(request);

    // manually deserialise back to OrderDto using the DynamoDBContext
    return _context.FromDocuments&lt;OrderDto&gt;(
        response.Items.Select(Document.FromAttributeMap)
    ).ToList();
}
</code></pre>
<p>Looking at the code above, notice that:</p>
<ul>
<li><p>Both partition key attributes (<code>customerId</code> and <code>status</code>) are supplied with equality — this is required.</p>
</li>
<li><p>The sort key (<code>createdAt</code>) uses an inequality <code>&gt;</code> (greater than) as the final condition, which is allowed.</p>
</li>
<li><p>No synthetic string construction, no brittle formatting conventions, no backfilling existing records.</p>
</li>
</ul>
<p>If you're working on an existing codebase that uses synthetic keys, it's worth evaluating whether migrating to multi-attribute GSIs makes sense. The backfilling problem that made migrations painful before is gone, DynamoDb multi-attribute GSIs handle it automatically.</p>
<h2 id="heading-query-vs-scan">Query vs Scan</h2>
<p>This is one of the most important concepts to understand when working with DynamoDB — and one of the most common sources of performance and cost problems.</p>
<h3 id="heading-query">Query</h3>
<p>A <code>Query</code> retrieves items using the partition key, and optionally narrows results using the sort key. DynamoDB knows exactly which partition to look in, reads only the relevant items, and returns them efficiently.</p>
<pre><code class="language-csharp">// Get all orders for a customer
var orders = await _context
    .QueryAsync&lt;OrderDto&gt;("customer-123")
    .GetRemainingAsync();
</code></pre>
<p>You can narrow further using a sort key condition — for example, all orders placed in the last 30 days:</p>
<pre><code class="language-csharp">var thirtyDaysAgo = DateTimeOffset.UtcNow.AddDays(-30).ToUnixTimeMilliseconds();

var orders = await _context.QueryAsync&lt;OrderDto&gt;(
    "customer-123",
    QueryOperator.GreaterThan,
    new List&lt;object&gt; { thirtyDaysAgo }
).GetRemainingAsync();
</code></pre>
<p>Queries are fast and cheap — you only pay RCUs for the records actually read.</p>
<h3 id="heading-scan">Scan</h3>
<p>A <code>Scan</code> reads every single item in the table, then filters the results. It doesn't use keys or indexes — it brute-forces through everything.</p>
<pre><code class="language-csharp">var conditions = new List&lt;ScanCondition&gt;
{
    new ScanCondition("status", ScanOperator.Equal, "pending")
};

var orders = await _context
    .ScanAsync&lt;OrderDto&gt;(conditions)
    .GetRemainingAsync();
</code></pre>
<p>This works — but on a table with 10 million orders, DynamoDB reads all 10 million records and then filters down to the pending ones. You pay RCUs for every single record read, not just the ones returned.</p>
<p><strong>Important:</strong> Scans should be avoided in production for large tables. They're slow, expensive, and get worse as your table grows.</p>
<p>The difference visualised:</p>
<pre><code class="language-plaintext">Query:
Table [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]
       └── Jump straight to partition "customer-123"
               └── Read only these items — cheap

Scan:
Table [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]
       └── Read every single item — expensive
               └── Then discard non-matching items
</code></pre>
<h3 id="heading-when-is-a-scan-acceptable">When Is A Scan Acceptable?</h3>
<p>Scans aren't always wrong — there are legitimate use cases:</p>
<ul>
<li><p><strong>Small tables</strong> — a lookup table with 50 items, a scan is perfectly fine</p>
</li>
<li><p><strong>One-off data migrations or admin scripts</strong> — not user-facing, run occasionally</p>
</li>
<li><p><strong>Development and debugging</strong> — scanning locally or against a small dataset</p>
</li>
</ul>
<p><strong>Rule of thumb:</strong> if it's a user-facing query on a growing table, it should be a Query, not a Scan.</p>
<p>Coming from SQL, developers often reach for a Scan because it feels like:</p>
<pre><code class="language-sql">SELECT * FROM orders WHERE status = 'pending'
</code></pre>
<p>In SQL with a good index, that's fine. In DynamoDB, without a GSI on <code>status</code>, that's a full table scan every time. The solution is to design a GSI for the access patterns you need — exactly what we covered in the GSI section earlier.</p>
<h2 id="heading-filter-expressions">Filter Expressions</h2>
<p>Both Query and Scan support an optional <code>FilterExpression</code> — a condition applied <em>after</em> DynamoDB has read the records but <em>before</em> they're returned to you. It looks superficially like a SQL <code>WHERE</code> clause, and that's exactly the trap.</p>
<pre><code class="language-csharp">var request = new QueryRequest
{
    TableName = "Orders",
    KeyConditionExpression = "customerId = :customerId",
    FilterExpression = "#status = :status",
    ExpressionAttributeNames = new Dictionary&lt;string, string&gt;
    {
        { "#status", "status" }
    },
    ExpressionAttributeValues = new Dictionary&lt;string, AttributeValue&gt;
    {
        { ":customerId", new AttributeValue { S = "customer-123" } },
        { ":status",     new AttributeValue { S = "pending" } }
    }
};
</code></pre>
<p>The critical thing to understand: <code>FilterExpression</code> does <strong>not</strong> reduce the cost of the query. DynamoDB still reads every record first, charges you RCUs for all of them, and only then discards the ones that don't match the filter.</p>
<p>For a Query, that means every record matched by the <code>KeyConditionExpression</code>. For a Scan, that means every record in the table.</p>
<p>It's a convenience for trimming the <em>response payload</em>, not a tool for efficient querying. If you find yourself reaching for <code>FilterExpression</code> to support a real access pattern, that's a signal you need a GSI instead.</p>
<h2 id="heading-paging-results-and-user-interfaces">Paging Results and User Interfaces</h2>
<p>Pagination is one of the most misunderstood aspects of DynamoDB, especially if you're coming from a SQL background.</p>
<p>In SQL you might write:</p>
<pre><code class="language-sql">SELECT * FROM orders LIMIT 10 OFFSET 20
</code></pre>
<p>DynamoDB doesn't work like this. There is no concept of <code>OFFSET</code> or page numbers. Instead, DynamoDB uses cursor-based pagination via a <code>LastEvaluatedKey</code>.</p>
<h3 id="heading-how-it-works-in-dynamodb">How It Works In DynamoDB</h3>
<p>DynamoDB returns a maximum of 1MB of data per request. If your results exceed 1MB, DynamoDB returns a <code>LastEvaluatedKey</code> — a pointer to where it stopped reading. Pass this back in to the next request to continue from that position. When no <code>LastEvaluatedKey</code> is returned, you've reached the end of the data.</p>
<h3 id="heading-how-this-works-in-the-dynamodb-c-sdk">How This Works In The DynamoDB C# SDK</h3>
<p>The SDK's <code>GetRemainingAsync()</code> method handles pagination automatically, it keeps making requests until there is no <code>LastEvaluatedKey</code> left, returning everything as a single list:</p>
<pre><code class="language-csharp">// Handles all pages automatically — but loads everything into memory
var orders = await _context
    .QueryAsync&lt;OrderDto&gt;("customer-123")
    .GetRemainingAsync();
</code></pre>
<p>This is convenient but dangerous on large datasets. If a customer has 50,000 orders, you're loading all 50,000 into memory in one go.</p>
<h3 id="heading-manual-pagination-the-right-approach-for-uis">Manual Pagination — The Right Approach For UIs</h3>
<p>For a UI with "load more" or "next/previous" navigation, control pagination manually using <code>GetNextSetAsync()</code>:</p>
<pre><code class="language-csharp">// ---- Paging Model ----
public class PagedResult&lt;T&gt;
{
    public List&lt;T&gt; Items { get; set; }
    public string? PaginationToken { get; set; }
}

// ---- Repository Method ----
public async Task&lt;PagedResult&lt;OrderDto&gt;&gt; GetOrdersPageAsync(
    string customerId,
    string? paginationToken = null)
{
    var config = new DynamoDBOperationConfig
    {
        BackwardQuery = true // reverse sort key order — newest first if sort key is a timestamp
    };

    var search = _context.QueryAsync&lt;OrderDto&gt;(customerId, config);

    if (paginationToken != null)
        search.PaginationToken = paginationToken;

    var items = await search.GetNextSetAsync(25); // fetch exactly 25 records

    return new PagedResult&lt;OrderDto&gt;
    {
        Items = items,
        PaginationToken = search.PaginationToken // null if no more pages
    };
}
</code></pre>
<p>The <code>PaginationToken</code> is the SDK's serialised representation of the <code>LastEvaluatedKey</code> — pass it directly to the client as a string and receive it back on the next request.</p>
<h3 id="heading-what-about-go-to-to-page-7-navigation">What About "go to to page 7" Navigation?</h3>
<p>This isn't possible in DynamoDB. The <code>LastEvaluatedKey</code> is a position cursor, to reach page 7 you'd have to paginate through pages 1 to 6 first to obtain the correct cursor placement.</p>
<p>For most modern UIs this isn't a problem. Infinite scroll and "load more" patterns map naturally to cursor-based pagination.</p>
<h3 id="heading-the-filterexpression-trap">The <code>FilterExpression</code> Trap</h3>
<p>We've already seen that <code>FilterExpression</code> is a poor substitute for a well-designed GSI. Pagination is where it goes from "wasteful" to actively broken.</p>
<p>DynamoDB's pagination works in two stages when a <code>FilterExpression</code> is involved:</p>
<ol>
<li><p>Read records until the 1MB limit is reached</p>
</li>
<li><p>Apply the <code>FilterExpression</code>, discarding non-matching records</p>
</li>
</ol>
<p>The <code>LastEvaluatedKey</code> is generated <strong>after step 1 — before filtering</strong>. So DynamoDB can return a <code>LastEvaluatedKey</code> implying there are more results, even if the filtered page returned only a handful of records.</p>
<p>With 1,000 orders where only 50 are "pending":</p>
<pre><code class="language-plaintext">Page 1: Read 200 records → filter applied → 3 "pending" returned + LastEvaluatedKey

Page 2: Read 200 records → filter applied → 1 "pending" returned + LastEvaluatedKey

Page 3: Read 200 records → filter applied → 0 "pending" returned + LastEvaluatedKey

...and so on until all 1,000 records are read
</code></pre>
<p><strong>Important:</strong> You pay RCUs for every record <strong>read</strong>, NOT every record <strong>returned</strong>.</p>
<p>The <code>Limit</code> parameter doesn't rescue you here. <code>GetNextSetAsync(25)</code> caps the records <em>read</em> before filtering, not the records <em>returned</em>. You can read 25, filter down to 3, and still get a <code>LastEvaluatedKey</code> back, meaning your "page size of 25" actually returns somewhere between 0 and 25 results, unpredictably.</p>
<p>The real fix isn't a smarter pagination strategy, it's removing the <code>FilterExpression</code> entirely. Design a GSI keyed on the attribute you're filtering by (here, <code>status</code>, or a multi-attribute GSI with <code>status</code> in the partition key). DynamoDB then reads only the matching records directly, <code>Limit</code> caps what you actually want, and pagination behaves predictably.</p>
<h2 id="heading-final-thoughts-amp-conclusion">Final Thoughts &amp; Conclusion</h2>
<p>DynamoDB rewards you for designing around your access patterns up front, and punishes you for pretending it's SQL. The two biggest shifts from a relational mindset are:</p>
<ol>
<li><p><strong>Queries are the schema.</strong> You model tables, keys, and GSIs around the queries you need to run. You don't normalise and figure out queries later.</p>
</li>
<li><p><strong>Keys do the work.</strong> The Query operation is fast and cheap precisely because it uses the partition key to jump straight to the right data. Scans read everything, and they get worse as your data grows.</p>
</li>
</ol>
<p>The November 2025 multi-attribute GSI release is a genuinely welcome change, and is a huge improvement to the AWS resource. It removes one of the most painful ergonomic issues with DynamoDB, synthetic key construction and backfilling, without loosening the constraints that make DynamoDB fast.</p>
<p>The query rules (all partition-key attributes supplied, sort-key attributes queried left-to-right) stay exactly the same. What you gain is cleaner, typed, natural data models and the ability to add new access patterns to existing tables without a data migration.</p>
<p>For new projects, my recommendation is to use multi-attribute GSIs by default. For existing codebases built on synthetic keys, evaluate whether a migration makes sense, the painful part of such migrations is now gone.</p>
<p>As always if you want to discuss this further, or hear about my other articles drop me a follow on <a href="https://x.com/grantdotdev">'X'</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Cloud-Native Development with Azure DevOps CI/CD Pipelines in Enterprise .NET Applications ]]>
                </title>
                <description>
                    <![CDATA[ Cloud-native development enables enterprise .NET applications to scale and remain resilient in the cloud. Using Azure DevOps CI/CD pipelines, you can automate building, testing, and deploying applicat ]]>
                </description>
                <link>https://www.freecodecamp.org/news/cloud-native-development-with-azure-devops-ci-cd-pipelines-in-enterprise-net-applications/</link>
                <guid isPermaLink="false">69c6a9e49aa3928a58bfd382</guid>
                
                    <category>
                        <![CDATA[ azure-devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Azure Pipelines ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CI/CD pipelines ]]>
                    </category>
                
                    <category>
                        <![CDATA[ dotnet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ kubernetes-services ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Containerizing .NET Applications ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Observability and Monitoring ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gopinath Karunanithi ]]>
                </dc:creator>
                <pubDate>Fri, 27 Mar 2026 16:00:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/eada90ad-0d90-4287-acee-2e544980ed4a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Cloud-native development enables enterprise .NET applications to scale and remain resilient in the cloud. Using Azure DevOps CI/CD pipelines, you can automate building, testing, and deploying applications, package them as Docker containers, and orchestrate deployments on platforms like Azure Kubernetes Service (AKS).</p>
<p>This approach ensures consistent, reliable releases across multiple environments while supporting best practices like infrastructure as code, security scanning, and observability. It can help your organization deliver cloud-ready .NET software efficiently and safely.</p>
<p>In this article, you'll learn how to implement cloud-native CI/CD pipelines for enterprise .NET applications using Azure DevOps, Docker, and Kubernetes.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-overview">Overview</a></p>
</li>
<li><p><a href="#heading-principles-of-cloud-native-net-development">Principles of Cloud-Native .NET Development</a></p>
</li>
<li><p><a href="#heading-understanding-azure-devops-cicd-pipelines">Understanding Azure DevOps CI/CD Pipelines</a></p>
</li>
<li><p><a href="#heading-creating-an-azure-devops-pipeline-for-a-net-application">Creating an Azure DevOps Pipeline for a .NET Application</a></p>
</li>
<li><p><a href="#heading-containerizing-net-applications">Containerizing .NET Applications</a></p>
</li>
<li><p><a href="#heading-building-and-publishing-docker-images-in-azure-devops">Building and Publishing Docker Images in Azure DevOps</a></p>
</li>
<li><p><a href="#heading-deploying-to-azure-kubernetes-service-aks">Deploying to Azure Kubernetes Service (AKS)</a></p>
</li>
<li><p><a href="#heading-managing-environments-with-deployment-stages">Managing Environments with Deployment Stages</a></p>
</li>
<li><p><a href="#heading-infrastructure-as-code-with-azure-devops">Infrastructure as Code with Azure DevOps</a></p>
</li>
<li><p><a href="#heading-implementing-security-in-cicd-pipelines">Implementing Security in CI/CD Pipelines</a></p>
</li>
<li><p><a href="#heading-observability-and-monitoring">Observability and Monitoring</a></p>
</li>
<li><p><a href="#heading-best-practices-for-enterprise-cicd-pipelines">Best Practices for Enterprise CI/CD Pipelines</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before moving into cloud-native development with Azure DevOps CI/CD pipelines, it’s helpful to have a basic understanding of the following concepts:</p>
<ul>
<li><p>Familiarity with building applications using <a href="http://ASP.NET">ASP.NET</a> Core or .NET (for example, controllers, APIs, project structure).</p>
</li>
<li><p>Understanding how to clone repositories, create branches, and push code changes.</p>
</li>
<li><p>Ability to run commands such as dotnet build, docker build, and kubectl.</p>
</li>
<li><p>Understanding what Continuous Integration and Continuous Deployment mean and why they're important.</p>
</li>
<li><p>Basic idea of how containerization works and how Docker packages applications.</p>
</li>
<li><p>Familiarity with Microsoft Azure or similar cloud providers.</p>
</li>
</ul>
<p>To work through the examples, you should also have the following tools:</p>
<ul>
<li><p>.NET SDK (version 6 or later recommended)</p>
</li>
<li><p>Docker installed locally</p>
</li>
<li><p>Azure DevOps account</p>
</li>
<li><p>Azure CLI (optional but useful)</p>
</li>
<li><p>A code editor like VS Code or Visual Studio</p>
</li>
</ul>
<h2 id="heading-overview">Overview</h2>
<p>Modern enterprise applications must be built to scale, adapt, and deploy quickly. Traditional monolithic deployment strategies (where applications are manually built, tested, and deployed) are no longer sufficient for teams delivering software in fast-paced environments. Organizations now expect rapid feature delivery, automated testing, and resilient infrastructure.</p>
<p>Cloud-native development addresses these challenges by embracing automation, microservices architectures, containerization, and continuous delivery. For .NET teams building enterprise applications, combining cloud-native principles with Azure DevOps CI/CD pipelines provides a powerful way to automate software delivery and improve reliability.</p>
<p>Azure DevOps enables teams to create fully automated pipelines that build, test, and deploy applications across environments. When integrated with .NET applications and cloud platforms such as Azure Kubernetes Service (AKS) or Azure App Service, CI/CD pipelines become the backbone of cloud-native development workflows.</p>
<p>By the end of this guide, you will understand how to implement a cloud-native delivery pipeline for .NET applications using Azure DevOps.</p>
<h2 id="heading-principles-of-cloud-native-net-development">Principles of Cloud-Native .NET Development</h2>
<p>Cloud-native applications are designed specifically to run in <strong>dynamic cloud environments</strong>. Rather than treating the cloud as simply another hosting location, cloud-native systems take advantage of elasticity, automation, and distributed architecture.</p>
<p>Key principles include:</p>
<h3 id="heading-microservices-architecture">Microservices Architecture</h3>
<p>Modern .NET applications are often split into smaller independent services. Each microservice can be deployed, scaled, and updated independently. This reduces system coupling and allows teams to deploy features faster.</p>
<h3 id="heading-stateless-services">Stateless services</h3>
<p>Cloud-native services typically avoid storing session data locally. Instead, state is stored in distributed databases, caches, or storage services. This allows applications to scale horizontally across multiple instances.</p>
<h3 id="heading-containerization">Containerization</h3>
<p>Containers package applications along with their dependencies, ensuring consistent execution across environments. Technologies like Docker allow .NET services to run identically in development, testing, and production.</p>
<h3 id="heading-infrastructure-as-code">Infrastructure as Code</h3>
<p>Cloud infrastructure is defined declaratively using templates such as Bicep, ARM, or Terraform. This ensures environments are reproducible, version-controlled, and automated.</p>
<h3 id="heading-observability-and-resilience">Observability and Resilience</h3>
<p>Cloud-native applications must be observable through logs, metrics, and traces. They also require resilience patterns such as retries, circuit breakers, and health checks.</p>
<p>Azure DevOps pipelines help enforce these principles by automating builds, deployments, and operational processes.</p>
<h2 id="heading-understanding-azure-devops-cicd-pipelines">Understanding Azure DevOps CI/CD Pipelines</h2>
<p>Azure DevOps pipelines automate the process of building, testing, and deploying applications.</p>
<p>A typical pipeline consists of two stages:</p>
<ul>
<li><p>Continuous Integration (CI)</p>
</li>
<li><p>Continuous Deployment (CD)</p>
</li>
</ul>
<h3 id="heading-continuous-integration-ci">Continuous Integration (CI)</h3>
<p>Continuous Integration ensures that every code change is automatically built and tested.</p>
<p>When developers push code to the repository, the pipeline:</p>
<ul>
<li><p>Restores dependencies</p>
</li>
<li><p>Compiles the application</p>
</li>
<li><p>Runs automated tests</p>
</li>
<li><p>Produces build artifacts</p>
</li>
</ul>
<p>This process helps teams detect issues early and maintain code quality.</p>
<h3 id="heading-continuous-deployment-cd">Continuous Deployment (CD)</h3>
<p>Continuous Deployment takes the build artifacts and deploys them to different environments, such as:</p>
<ul>
<li><p>Development</p>
</li>
<li><p>Staging</p>
</li>
<li><p>Production</p>
</li>
</ul>
<p>Deployment can include infrastructure provisioning, container image publishing, and application rollout.</p>
<h2 id="heading-creating-an-azure-devops-pipeline-for-a-net-application">Creating an Azure DevOps Pipeline for a .NET Application</h2>
<p>Azure DevOps uses YAML pipelines to define automation workflows.</p>
<p>Let’s start with a simple pipeline configuration for building a .NET application.</p>
<pre><code class="language-yaml">trigger:
- main

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
- task: UseDotNet@2
  inputs:
    packageType: 'sdk'
    version: '8.0.x'

- script: dotnet restore
  displayName: Restore dependencies

- script: dotnet build --configuration $(buildConfiguration)
  displayName: Build project

- script: dotnet test --configuration $(buildConfiguration)
  displayName: Run unit tests

- script: dotnet publish -c \((buildConfiguration) -o \)(Build.ArtifactStagingDirectory)
  displayName: Publish application

- task: PublishBuildArtifacts@1

 inputs:
    pathToPublish: $(Build.ArtifactStagingDirectory)
    artifactName: drop
</code></pre>
<p>This pipeline performs the following tasks:</p>
<ol>
<li><p>Installs the .NET SDK</p>
</li>
<li><p>Restores NuGet dependencies</p>
</li>
<li><p>Builds the application</p>
</li>
<li><p>Runs tests</p>
</li>
<li><p>Publishes build artifacts</p>
</li>
</ol>
<p>Once the artifacts are generated, they can be deployed automatically.</p>
<h2 id="heading-containerizing-net-applications">Containerizing .NET Applications</h2>
<p>Cloud-native systems commonly use containers to ensure consistency across environments.</p>
<p>Docker allows you to package your .NET application and its dependencies into a container image.</p>
<p>Here is an example Dockerfile for an <a href="http://ASP.NET">ASP.NET</a> Core application:</p>
<pre><code class="language-dockerfile">FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY . .
RUN dotnet restore
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .

ENTRYPOINT ["dotnet", "MyApp.dll"]
</code></pre>
<p>This Dockerfile uses a multi-stage build to keep the final container image lightweight.</p>
<p>The application is compiled in the build stage and then copied into a smaller runtime image.</p>
<h2 id="heading-building-and-publishing-docker-images-in-azure-devops">Building and Publishing Docker Images in Azure DevOps</h2>
<p>After containerizing your application, the pipeline can automatically build and push the container image.</p>
<pre><code class="language-yaml">- task: Docker@2
  displayName: Build and push Docker image
  inputs:
    command: buildAndPush
    repository: myregistry.azurecr.io/myapp
    dockerfile: Dockerfile
    containerRegistry: MyAzureContainerRegistry
    tags: |
      $(Build.BuildId)
      latest
</code></pre>
<p>This step performs two important actions:</p>
<ol>
<li><p>Builds the Docker image</p>
</li>
<li><p>Pushes it to Azure Container Registry (ACR)</p>
</li>
</ol>
<p>Once stored in ACR, the image can be deployed to various environments.</p>
<h2 id="heading-deploying-to-azure-kubernetes-service-aks">Deploying to Azure Kubernetes Service (AKS)</h2>
<p>Kubernetes is a popular orchestration platform for cloud-native applications.</p>
<p>Azure Kubernetes Service (AKS) simplifies Kubernetes deployment and management.</p>
<p>To deploy the application, you can use a Kubernetes deployment manifest.</p>
<p>Example deployment.yaml:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: dotnet-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dotnet-app
template:
    metadata:
      labels:
        app: dotnet-app
spec:
      containers:
      - name: dotnet-app
        image: myregistry.azurecr.io/myapp:latest
        ports:
        - containerPort: 80
</code></pre>
<p>This configuration defines:</p>
<ul>
<li><p>A deployment with three replicas</p>
</li>
<li><p>The container image to run</p>
</li>
<li><p>The exposed port</p>
</li>
</ul>
<p>Now we add a pipeline step to deploy it.</p>
<pre><code class="language-yaml">- task: KubernetesManifest@0
  inputs:
    action: deploy
    kubernetesServiceConnection: myKubernetesConnection
    namespace: default
    manifests: deployment.yaml
</code></pre>
<p>This step updates the Kubernetes cluster with the latest version of the application.</p>
<h2 id="heading-managing-environments-with-deployment-stages">Managing Environments with Deployment Stages</h2>
<p>Enterprise pipelines often include multiple environments.</p>
<p>For example:</p>
<ul>
<li><p>Development</p>
</li>
<li><p>QA</p>
</li>
<li><p>Production.</p>
</li>
</ul>
<p>Azure DevOps allows pipelines to define deployment stages.</p>
<p><strong>Example:</strong></p>
<pre><code class="language-yaml">stages:
- stage: Build
 jobs:
  - job: BuildApp
  steps:
      - script: dotnet build

  - stage: DeployDev
  dependsOn: Build
  jobs:
  - deployment: DeployDev
    environment: dev
    strategy:
     runOnce:
       deploy:
        steps:
          - script: echo Deploying to Dev
- stage: DeployProd
  dependsOn: DeployDev
  jobs:
  - deployment: DeployProd
    environment: production
</code></pre>
<p>Stages allow teams to:</p>
<ul>
<li><p>Approve deployments</p>
</li>
<li><p>Run environment-specific tests</p>
</li>
<li><p>Control promotion between environments</p>
</li>
</ul>
<h2 id="heading-infrastructure-as-code-with-azure-devops">Infrastructure as Code with Azure DevOps</h2>
<p>Cloud-native environments require automated infrastructure provisioning.</p>
<p>Tools such as Terraform, Bicep, or ARM templates allow infrastructure to be defined as code.</p>
<p>Example Terraform pipeline step:</p>
<pre><code class="language-markdown">- script: |
    terraform init
    terraform plan
    terraform apply -auto-approve
  displayName: Provision infrastructure
</code></pre>
<p>This ensures infrastructure environments remain consistent and reproducible.</p>
<h2 id="heading-implementing-security-in-cicd-pipelines">Implementing Security in CI/CD Pipelines</h2>
<p>Security in CI/CD pipelines should be automated and enforced at every stage of the delivery lifecycle. Instead of treating security as a separate step, modern pipelines integrate security checks directly into build and deployment workflows.</p>
<p>Below are practical implementations of key security practices.</p>
<h3 id="heading-1-secure-secrets-with-azure-key-vault">1. Secure Secrets with Azure Key Vault</h3>
<p>Never hardcode secrets such as API keys, connection strings, or credentials in your codebase.</p>
<p>In Azure DevOps, you can link secrets from Azure Key Vault using variable groups.</p>
<p><strong>Pipeline Example</strong></p>
<pre><code class="language-yaml">variables:
- group: KeyVault-Secrets
</code></pre>
<h4 id="heading-usage-in-code">Usage in Code</h4>
<p>var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION");</p>
<p>This ensures:</p>
<ul>
<li><p>Secrets are stored securely</p>
</li>
<li><p>No sensitive data is exposed in source control</p>
</li>
<li><p>Secrets can be rotated without code changes</p>
</li>
</ul>
<h3 id="heading-2-dependency-vulnerability-scanning">2. Dependency Vulnerability Scanning</h3>
<p>Automatically scan for vulnerable packages during the build process.</p>
<p><strong>Pipeline Step:</strong></p>
<pre><code class="language-yaml">- script: dotnet list package --vulnerable
  displayName: Check for vulnerable dependencies
</code></pre>
<p><strong>Example Output (What You’ll See)</strong></p>
<table>
<thead>
<tr>
<th>Package</th>
<th>Requested</th>
<th>Resolved</th>
<th>Severity</th>
</tr>
</thead>
<tbody><tr>
<td>Newtonsoft.Json</td>
<td>12.0.1</td>
<td>12.0.1</td>
<td>High</td>
</tr>
</tbody></table>
<p>This allows teams to:</p>
<ul>
<li><p>Detect vulnerabilities early</p>
</li>
<li><p>Prevent insecure builds from progressing</p>
</li>
</ul>
<h3 id="heading-3-static-code-analysis">3. Static Code Analysis</h3>
<p>Use tools like SonarCloud or built-in analyzers to catch security issues.</p>
<p><strong>Example Pipeline Step:</strong></p>
<pre><code class="language-yaml">- task: SonarCloudAnalyze@1
</code></pre>
<p>This can detect:</p>
<ul>
<li><p>SQL injection risks</p>
</li>
<li><p>Hardcoded credentials</p>
</li>
<li><p>Unsafe API usage</p>
</li>
</ul>
<h3 id="heading-4-enforcing-secure-build-policies">4. Enforcing Secure Build Policies</h3>
<p>You can enforce rules such as:</p>
<ul>
<li><p>Blocking builds with vulnerabilities</p>
</li>
<li><p>Requiring pull request approvals</p>
</li>
</ul>
<p><strong>Example – Fail Pipeline on Vulnerabilities:</strong></p>
<pre><code class="language-yaml">- script: |
    dotnet list package --vulnerable | grep "High" &amp;&amp; exit 1 || echo "No        high vulnerabilities"
  displayName: Fail on high vulnerabilities
</code></pre>
<h3 id="heading-5-container-security-scanning">5. Container Security Scanning</h3>
<p>Scan Docker images before deployment.</p>
<pre><code class="language-yaml">- task: Docker@2
  displayName: Scan Docker Image
  inputs:
    command: build
</code></pre>
<p>You can integrate tools like Trivy or Microsoft Defender for Containers.</p>
<h2 id="heading-observability-and-monitoring">Observability and Monitoring</h2>
<p>Cloud-native applications require strong observability.</p>
<p>Observability ensures that you can understand how your application behaves in production. In cloud-native systems, it is essential for debugging, performance optimization, and reliability.</p>
<p>A complete observability strategy includes:</p>
<ul>
<li><p>Logs</p>
</li>
<li><p>Metrics</p>
</li>
<li><p>Traces</p>
</li>
</ul>
<h3 id="heading-1-adding-application-insights-to-a-net-app">1. Adding Application Insights to a .NET App</h3>
<p>Azure Application Insights provides built-in telemetry for .NET applications.</p>
<p><strong>Setup in</strong> <strong>ASP.NET</strong> <strong>Core:</strong></p>
<pre><code class="language-csharp">builder.Services.AddApplicationInsightsTelemetry();
</code></pre>
<p>Example: Custom Logging</p>
<pre><code class="language-csharp">private readonly ILogger&lt;HomeController&gt; _logger;

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

public IActionResult Index()
{
    _logger.LogInformation("Home page accessed");
    return View();
}
</code></pre>
<p>In Azure Portal, this appears as:</p>
<ul>
<li><p>Request logs</p>
</li>
<li><p>Response times</p>
</li>
<li><p>Dependency tracking</p>
</li>
</ul>
<h3 id="heading-2-tracking-custom-metrics">2. Tracking Custom Metrics</h3>
<p>You can track business-specific metrics.</p>
<pre><code class="language-csharp">var telemetryClient = new TelemetryClient();

telemetryClient.TrackMetric("OrdersProcessed", 1);
</code></pre>
<p><strong>Example use cases:</strong></p>
<ul>
<li><p>Number of API calls</p>
</li>
<li><p>Orders processed</p>
</li>
<li><p>Failed transactions</p>
</li>
</ul>
<h3 id="heading-3-distributed-tracing-example">3. Distributed Tracing Example</h3>
<p>Tracing helps track requests across services.</p>
<pre><code class="language-csharp">using System.Diagnostics;

var activity = new Activity("ProcessOrder");
activity.Start();

// Business logic here

activity.Stop();
</code></pre>
<p>This allows you to:</p>
<ul>
<li><p>Trace request flow across microservices</p>
</li>
<li><p>Identify bottlenecks</p>
</li>
</ul>
<h3 id="heading-4-observability-in-cicd-pipelines">4. Observability in CI/CD Pipelines</h3>
<p>You can also monitor pipeline execution itself.</p>
<p>Example: Logging in Pipeline</p>
<pre><code class="language-yaml">- script: echo "Deploying version $(Build.BuildId)"
  displayName: Log deployment version
</code></pre>
<p>Example: Tracking Deployment Time</p>
<pre><code class="language-yaml">- script: date
  displayName: Start Time

- script: echo "Deploying..."

- script: date
  displayName: End Time
</code></pre>
<h3 id="heading-5-monitoring-kubernetes-deployments">5. Monitoring Kubernetes Deployments</h3>
<p>If using AKS, monitor pods and services.</p>
<pre><code class="language-markdown">kubectl get pods
kubectl logs &lt;pod-name&gt;
</code></pre>
<p>This helps identify:</p>
<ul>
<li><p>Crashes</p>
</li>
<li><p>Restart loops</p>
</li>
<li><p>Performance issues</p>
</li>
<li><p>What Observability Looks Like in Practice</p>
</li>
</ul>
<p>In a real system, observability enables you to answer questions like</p>
<ul>
<li><p>Why is this request slow?</p>
</li>
<li><p>Which service failed?</p>
</li>
<li><p>What changed in the last deployment?</p>
</li>
</ul>
<p><strong>For example:</strong></p>
<p>A spike in latency can be traced to a slow database query. Increased errors can be linked to a recent deployment. And high CPU usage can be caused by inefficient code.</p>
<h2 id="heading-best-practices-for-enterprise-cicd-pipelines">Best Practices for Enterprise CI/CD Pipelines</h2>
<p>Designing CI/CD pipelines for enterprise .NET applications requires more than automation—it requires consistency, reliability, and control. Below are key best practices with real examples to show how they are implemented in practice.</p>
<h3 id="heading-1-keep-pipelines-declarative-yaml-based">1. Keep Pipelines Declarative (YAML-Based)</h3>
<p>Defining pipelines in YAML ensures they are version-controlled and reproducible.</p>
<p>Example:</p>
<pre><code class="language-yaml">trigger:
- main

pool:
  vmImage: 'ubuntu-latest'

steps:
- script: dotnet build
</code></pre>
<p>This approach allows you to:</p>
<ul>
<li><p>Track pipeline changes in Git</p>
</li>
<li><p>Review pipeline updates via pull requests</p>
</li>
<li><p>Reuse templates across projects</p>
</li>
</ul>
<h3 id="heading-2-implement-automated-testing-at-multiple-levels">2. Implement Automated Testing at Multiple Levels</h3>
<p>A robust pipeline includes unit, integration, and end-to-end tests.</p>
<p>Example:</p>
<pre><code class="language-yaml">- script: dotnet test --filter Category=Unit
  displayName: Run Unit Tests

- script: dotnet test --filter Category=Integration
  displayName: Run Integration Tests
</code></pre>
<p>This ensures that bugs are caught early, critical workflows are validated, and releases are more stable.</p>
<h3 id="heading-3-use-immutable-build-artifacts">3. Use Immutable Build Artifacts</h3>
<p>Build once and deploy the same artifact across all environments.</p>
<p>Example:</p>
<pre><code class="language-yaml">- script: dotnet publish -c Release -o $(Build.ArtifactStagingDirectory)

- task: PublishBuildArtifacts@1
  inputs:
    pathToPublish: $(Build.ArtifactStagingDirectory)
    artifactName: drop
</code></pre>
<p>Deployment Uses Same Artifact</p>
<pre><code class="language-yaml">- task: DownloadBuildArtifacts@0
  inputs:
    artifactName: drop
</code></pre>
<p>This prevents environment inconsistencies and “Works on my machine” issues.</p>
<h3 id="heading-4-enable-safe-deployment-strategies-blue-green-canary">4. Enable Safe Deployment Strategies (Blue-Green / Canary)</h3>
<p>Avoid deploying directly to all users at once.</p>
<p>Example: Canary Deployment Concept</p>
<pre><code class="language-yaml">- script: echo "Deploying to 10% of users"
</code></pre>
<p>In Kubernetes:</p>
<pre><code class="language-yaml">spec:
  replicas: 10
</code></pre>
<p>Then gradually increase replicas of the new version.</p>
<p>This allows:</p>
<ul>
<li><p>Gradual rollout</p>
</li>
<li><p>Early detection of failures</p>
</li>
<li><p>Quick rollback if needed</p>
</li>
</ul>
<h3 id="heading-5-enforce-pull-request-validation">5. Enforce Pull Request Validation</h3>
<p>Ensure code is tested before merging into main branches.</p>
<p>Example:</p>
<pre><code class="language-yaml">pr:
- main
steps:
- script: dotnet build
- script: dotnet test
</code></pre>
<p>This ensures that only validated code is merged, and that code quality remains high.</p>
<h3 id="heading-6-use-environment-approvals-for-production">6. Use Environment Approvals for Production</h3>
<p>Prevent accidental deployments to production.</p>
<p>Example:</p>
<pre><code class="language-yaml">- stage: DeployProd
  jobs:
- deployment: Deploy
    environment: production
</code></pre>
<p>Azure DevOps allows manual approvals and role-based access control.</p>
<p>This ensures that you have controlled releases and results in reduced risk.</p>
<h3 id="heading-7-version-your-builds-and-artifacts">7. Version Your Builds and Artifacts</h3>
<p>Every build should be uniquely identifiable.</p>
<p>Example:</p>
<pre><code class="language-yaml">- script: echo "Version: $(Build.BuildId)"
</code></pre>
<p>For Docker:</p>
<pre><code class="language-yaml">tags: |
  $(Build.BuildId)
  latest
</code></pre>
<p>This allows for easy rollbacks and traceability.</p>
<h3 id="heading-8-add-logging-and-diagnostics-in-pipelines">8. Add Logging and Diagnostics in Pipelines</h3>
<p>Pipelines should produce meaningful logs.</p>
<p>Example:</p>
<pre><code class="language-yaml">- script: echo "Starting deployment..."
- script: dotnet build
- script: echo "Build completed"
</code></pre>
<p>This helps you debug failed pipelines and understand execution flow.</p>
<h3 id="heading-9-automate-infrastructure-provisioning">9. Automate Infrastructure Provisioning</h3>
<p>Don't manually create infrastructure. Instead, use a tool like Terraform.</p>
<p>Example (Terraform):</p>
<pre><code class="language-yaml">- script: |
    terraform init
    terraform apply -auto-approve
</code></pre>
<p>This ensures consistent environments and repeatable deployments.</p>
<h3 id="heading-10-monitor-pipeline-and-deployment-performance">10. Monitor Pipeline and Deployment Performance</h3>
<p>Track metrics such as build time and deployment frequency.</p>
<p>Example:</p>
<pre><code class="language-yaml">- script: echo "Build completed at $(date)"
</code></pre>
<p>You can track:</p>
<ul>
<li><p>Build duration</p>
</li>
<li><p>Deployment success rate</p>
</li>
<li><p>Failure trends</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Cloud-native development has transformed how enterprise .NET applications are built and delivered. By adopting containerization, automated pipelines, and infrastructure as code, teams can deliver reliable software faster and with greater confidence.</p>
<p>Azure DevOps CI/CD pipelines play a central role in this process. They automate everything from building and testing applications to packaging containers and deploying them across cloud environments. When combined with technologies such as Docker, Kubernetes, and Azure monitoring services, they enable .NET teams to build scalable, resilient, and continuously deployable systems.</p>
<p>For teams beginning their cloud-native journey, the best starting point is to automate the build and test process using CI pipelines. From there, gradually introduce containerization, deployment automation, and infrastructure as code. As pipelines mature, organizations can incorporate advanced practices such as multi-environment deployments, automated security scanning, and progressive rollout strategies.</p>
<p>Ultimately, cloud-native CI/CD pipelines turn software delivery into a repeatable and reliable process. For enterprise .NET applications, this shift allows development teams to focus less on manual operations and more on delivering value through faster innovation and continuous improvement.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Improve Developer Experience in Microservices Applications with .NET Aspire ]]>
                </title>
                <description>
                    <![CDATA[ Since the advent of microservices, development teams have gained the flexibility to deploy services independently, without coordinating with the entire engineering organization. Bug fixes can be released in isolation without full regression testing, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/improve-developer-experience-with-net-aspire/</link>
                <guid isPermaLink="false">68fb8c95d81014dabb030226</guid>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ .NET ]]>
                    </category>
                
                    <category>
                        <![CDATA[ developer experience ]]>
                    </category>
                
                    <category>
                        <![CDATA[ dotnet ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Opaluwa Emidowojo ]]>
                </dc:creator>
                <pubDate>Fri, 24 Oct 2025 14:26:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761315727860/7321f413-ec87-47a8-b194-523c026f495b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Since the advent of microservices, development teams have gained the flexibility to deploy services independently, without coordinating with the entire engineering organization. Bug fixes can be released in isolation without full regression testing, and multiple teams can ship updates simultaneously, sometimes ten or more deploys a day per team.</p>
<p>But we rarely talk about the downsides of microservices. In medium to large-scale systems, the number of services can grow quickly. Netflix reportedly runs over seven hundred microservices, and Uber manages more than two thousand. That kind of scale introduces a lot of moving parts, testing complexity, and debugging challenges across service boundaries. And all of this can severely impact developer experience (DX).</p>
<p>Recently, I came across a new framework called <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview"><strong>.NET Aspire</strong></a>, which dramatically simplifies local microservices development. Aspire handles service discovery, configuration management, and observability for distributed applications, giving you a complete view of your system through a built-in dashboard. This results in a much simpler, smoother local development experience compared to manually wiring up multiple services. In this guide, we'll explore how Aspire works and how it can help improve developer experience in microservices-based systems.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, ensure you have the following installed:</p>
<ul>
<li><p><a target="_blank" href="https://dotnet.microsoft.com/download"><strong>.NET 8 SDK</strong></a> <strong>or later</strong></p>
</li>
<li><p><a target="_blank" href="https://www.docker.com/products/docker-desktop/"><strong>Docker Desktop</strong></a></p>
<ul>
<li><p>Aspire uses Docker to run dependencies like Redis, PostgreSQL, and so on.</p>
</li>
<li><p>Ensure Docker is running before starting</p>
</li>
</ul>
</li>
<li><p><strong>Visual Studio 2022 (v17.9+)</strong> or <strong>Visual Studio Code</strong> with C# Dev Kit</p>
</li>
<li><p><strong>Basic understanding of:</strong></p>
<ul>
<li><p>C# and .NET development</p>
</li>
<li><p>Microservices architecture concepts</p>
</li>
<li><p>REST APIs and service communication</p>
</li>
</ul>
</li>
</ul>
<p><strong>Optional but Recommended:</strong></p>
<ul>
<li><p>Familiarity with Docker and containerization</p>
</li>
<li><p>Experience with distributed application development</p>
</li>
<li><p>Knowledge of observability concepts (logging, tracing, metrics)</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-developer-experience-in-microservices">Understanding Developer Experience in Microservices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introducing-net-aspire">Introducing .NET Aspire</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-net-aspire-in-your-project">How to Set Up .NET Aspire in Your Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-matters-for-developer-experience">Why This Matters for Developer Experience</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-framework-how-to-adopt-net-aspire-incrementally">Framework: How to Adopt .NET Aspire Incrementally</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-the-net-aspire-dashboard">How to Use the .NET Aspire Dashboard</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-practical-scenarios-solving-real-world-dx-challenges-with-net-aspire">Practical Scenarios: Solving Real-World DX Challenges with .NET Aspire</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-going-further">Going Further</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-takeaways-and-when-to-use-net-aspire">Key Takeaways and When to Use .NET Aspire</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-and-when-not-to-use-net-aspire">When (and When Not) to Use .NET Aspire</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-understanding-developer-experience-in-microservices"><strong>Understanding Developer Experience in Microservices</strong></h2>
<p>When people talk about DX, they often think of it as tooling or ergonomics, things like good documentation, fast build times, and clean APIs. But in distributed systems, DX becomes much broader. It’s about how easily developers can set up, run, and reason about the systems they’re building.</p>
<p>In a monolithic application, starting your development environment might mean running a single command like <code>dotnet run</code>. But in a microservices-based system, you might need to start multiple APIs, databases, background workers, and queues, all with specific configuration dependencies. This extra overhead doesn’t just slow you down, it breaks your focus and adds friction to daily development.</p>
<p>Over time, that friction compounds.</p>
<ul>
<li><p>Onboarding new developers becomes slower.</p>
</li>
<li><p>Debugging across service boundaries gets harder.</p>
</li>
<li><p>Teams spend more time managing environments than writing features.</p>
</li>
</ul>
<p>That’s why DX is so important in microservices architectures. It is not just about developer happiness, it’s about velocity, consistency, and confidence. If your local environment isn’t easy to run or reason about, every other process in your development lifecycle suffers.</p>
<p>This is where orchestration frameworks like <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview"><strong>.NET Aspire</strong></a> start to make a real difference. They handle the complexity of coordinating services, so developers can focus on building and iterating faster, the way modern software development is meant to work.</p>
<h2 id="heading-introducing-net-aspire"><strong>Introducing .NET Aspire</strong></h2>
<p>As microservice systems grow, local development environments often become a patchwork of scripts, Docker Compose files, and manual setup steps. Each developer ends up managing their own version of “how to get things running,” and small differences in configuration can lead to big inconsistencies across teams.</p>
<p><strong>.NET Aspire</strong> is an orchestration framework designed to simplify this process. It provides a way to define, configure, and run your distributed applications as a single unit, directly within your .NET solution.</p>
<p>In practical terms, Aspire helps developers by handling three key areas automatically:</p>
<ol>
<li><p><strong>Service Orchestration</strong><br> Aspire can start multiple projects (APIs, workers, databases, and so on) in the correct order. It takes care of service dependencies so that, for example, your API doesn’t try to start before the database it depends on is ready.</p>
</li>
<li><p><strong>Configuration Management</strong><br> Instead of juggling dozens of <code>appsettings.json</code> files or environment variables, Aspire provides a centralized configuration model. It shares connection strings, ports, and environment settings across services in a consistent way.</p>
</li>
<li><p><strong>Observability and Insights</strong><br> Aspire includes built-in OpenTelemetry support and a dashboard that gives you real-time visibility into your running services, including their health, logs, and endpoints. This makes debugging and local monitoring much easier.</p>
</li>
</ol>
<p>In many ways, Aspire does for services what Kubernetes does for containers, but with a sharper focus on local development and developer experience. It’s not meant to replace your production orchestration tools, it’s designed to make your everyday development smoother, faster, and less error-prone.</p>
<h2 id="heading-how-to-set-up-net-aspire-in-your-project"><strong>How to Set Up .NET Aspire in Your Project</strong></h2>
<p>We'll create a microservices setup and watch Aspire orchestrate it with minimal code. Make sure you're running .NET 8 or later. Aspire requires it.</p>
<p><strong>Create a New Aspire Project</strong></p>
<p>Start by creating a new Aspire app host using the .NET CLI:</p>
<pre><code class="lang-csharp">dotnet <span class="hljs-keyword">new</span> aspire-app -n MyCompany.AppHost
</code></pre>
<p>This command creates a new Aspire “host” project, the entry point that orchestrates your other microservices, APIs, and dependencies.</p>
<p>You’ll notice that the generated project contains a <code>Program.cs</code> file with an <code>AppHostBuilder</code>. This builder acts as the control center for your distributed system.</p>
<p><strong>Add Your Microservices</strong></p>
<p>You can now reference your existing projects or create new ones directly in the same solution. For example:</p>
<pre><code class="lang-csharp">dotnet <span class="hljs-keyword">new</span> webapi -n CatalogService
dotnet <span class="hljs-keyword">new</span> webapi -n OrderService
dotnet <span class="hljs-keyword">new</span> worker -n NotificationWorker
</code></pre>
<p>Then, add them to your Aspire host by editing <code>Program.cs</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> builder = DistributedApplication.CreateBuilder(args);

<span class="hljs-keyword">var</span> catalog = builder.AddProject&lt;Projects.CatalogService&gt;(<span class="hljs-string">"catalog"</span>);
<span class="hljs-keyword">var</span> order = builder.AddProject&lt;Projects.OrderService&gt;(<span class="hljs-string">"order"</span>)
                   .WaitFor(catalog); <span class="hljs-comment">// ensure this starts after CatalogService</span>
<span class="hljs-keyword">var</span> notifications = builder.AddProject&lt;Projects.NotificationWorker&gt;(<span class="hljs-string">"notifications"</span>);

builder.Build().Run();
</code></pre>
<p>In this example:</p>
<ul>
<li><p><code>AddProject</code> registers each service with Aspire.</p>
</li>
<li><p><code>.WaitFor()</code> enforces startup dependencies (for example, <code>OrderService</code> depends on <code>CatalogService</code>).</p>
</li>
<li><p>Aspire takes care of starting these services in the right order, sharing environment variables, and managing ports automatically.</p>
</li>
</ul>
<p><strong>Run All Services with One Command</strong></p>
<p>Now, from your app host directory, run:</p>
<pre><code class="lang-csharp">dotnet run
</code></pre>
<p>Aspire will:</p>
<ul>
<li><p>Start all the registered services.</p>
</li>
<li><p>Allocate available ports.</p>
</li>
<li><p>Inject shared configurations.</p>
</li>
<li><p>Launch a local dashboard showing service health, endpoints, and logs.</p>
</li>
</ul>
<p>You should see output like this:</p>
<pre><code class="lang-csharp">Starting CatalogService...
Starting OrderService...
Starting NotificationWorker...
AppHost running <span class="hljs-keyword">on</span> http:<span class="hljs-comment">//localhost:18888</span>
</code></pre>
<p>And when you open the dashboard in your browser, you’ll see all your services, their statuses, and links to their APIs.</p>
<p><strong>Add a Local Database (Optional)</strong></p>
<p>To show how Aspire handles dependencies, let’s add a PostgreSQL container:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> db = builder.AddPostgres(<span class="hljs-string">"postgres"</span>);
builder.AddProject&lt;Projects.CatalogService&gt;(<span class="hljs-string">"catalog"</span>)
       .WithReference(db); <span class="hljs-comment">// injects connection string automatically</span>
</code></pre>
<p>Now when you run the app, Aspire will start PostgreSQL first, generate a connection string, and pass it to <code>CatalogService</code>. No manual setup or <code>.env</code> files required.</p>
<h2 id="heading-why-this-matters-for-developer-experience"><strong>Why This Matters for Developer Experience</strong></h2>
<p>Before Aspire, getting your local environment running meant opening multiple terminals, waiting around for databases to start, and copying connection strings between projects. With Aspire, it's just one command. Everything starts automatically, configuration is shared across services, and you get observability built in. That's the developer experience win. Less time fighting your setup, more time actually coding.</p>
<h2 id="heading-framework-how-to-adopt-net-aspire-incrementally"><strong>Framework: How to Adopt .NET Aspire Incrementally</strong></h2>
<p>If you’re considering trying Aspire in your own team, you don’t have to migrate everything at once. In fact, the best approach is incremental adoption. Start small and expand gradually.</p>
<p>Here’s a simple framework you can follow:</p>
<p><strong>Step 1: Start Small</strong></p>
<p>Create an Aspire host and connect one or two key services.<br>This helps your team understand the orchestration flow before scaling up.</p>
<pre><code class="lang-csharp">dotnet <span class="hljs-keyword">new</span> aspire-app -n MyCompany.AppHost
</code></pre>
<p><strong>Step 2: Add Dependencies Incrementally</strong></p>
<p>As you grow, include more services and use <code>.WaitFor()</code> to define dependencies and startup order.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> builder = DistributedApplication.CreateBuilder(args);

<span class="hljs-keyword">var</span> db = builder.AddPostgres(<span class="hljs-string">"postgres"</span>);
builder.AddProject&lt;Projects.CatalogService&gt;(<span class="hljs-string">"catalog"</span>)
       .WithReference(db);
builder.AddProject&lt;Projects.ApiGateway&gt;(<span class="hljs-string">"gateway"</span>)
       .WaitFor(<span class="hljs-string">"catalog"</span>);

builder.Build().Run();
</code></pre>
<p><strong>Step 3: Integrate Observability</strong></p>
<p>Leverage Aspire’s built-in <strong>OpenTelemetry</strong> integration for metrics and traces. You’ll instantly gain better insight into service interactions even without external tools.</p>
<p><strong>Step 4: Share Your Setup</strong></p>
<p>Commit your Aspire host to source control so every developer uses the same setup.<br>This ensures consistency across environments, reducing the classic “works on my machine” problem.</p>
<p><strong>Note</strong>: Aspire doesn’t require a full rewrite. It works great as a starting layer while your team continues evolving your existing orchestration setup.</p>
<h2 id="heading-how-to-use-the-net-aspire-dashboard"><strong>How to Use the .NET Aspire Dashboard</strong></h2>
<p>One of the standout features of .NET Aspire is its built-in dashboard, which gives you real-time visibility into your microservices while they run locally.</p>
<p>When you start your Aspire app host with <code>dotnet run</code>, it automatically spins up a local dashboard (by default at <a target="_blank" href="http://localhost:18888"><code>http://localhost:18888</code></a>). This dashboard provides a centralized view of all your services — APIs, databases, background workers, and any connected dependencies.</p>
<p>Here’s what you’ll find inside:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761073706691/bf60d044-4e73-4fdf-a276-a41f58d48fab.png" alt="Screenshot of the &quot;Resources&quot; view in the .NET Aspire dashboard named testhost. It shows three running resources, cache, apiservice, and webfrontend, each listed with their state, start time, source, and URLs. The cache service uses a Redis image from Docker Hub, while apiservice and webfrontend reference local project files (AspireSample.ApiService.csproj and AspireSample.Web.csproj). All three resources show a “Running” status with localhost URLs for access." class="image--center mx-auto" width="1280" height="400" loading="lazy"></p>
<h3 id="heading-service-overview"><strong>Service Overview</strong></h3>
<p>The dashboard home page lists every service in your distributed application. For each one, you can see:</p>
<ul>
<li><p><strong>Name and type</strong> (for example, cache, apiservice, webfrontend)</p>
</li>
<li><p><strong>Current state</strong> (Running, Starting, Stopped)</p>
</li>
<li><p><strong>Source</strong></p>
</li>
<li><p><strong>Port and endpoint</strong> information</p>
</li>
<li><p><strong>Startup time</strong> and uptime</p>
</li>
<li><p><strong>Logs and metrics shortcuts</strong></p>
</li>
</ul>
<p>This immediately replaces the need to track multiple terminal windows or scroll through dozens of logs just to confirm everything started correctly.</p>
<p>The dashboard automatically detects unhealthy or failed services and highlights them, so you can identify startup issues early.</p>
<h3 id="heading-navigating-to-endpoints"><strong>Navigating to Endpoints</strong></h3>
<p>Each service card includes quick links to its exposed endpoint, providing easy access to relevant tools and interfaces. For example, APIs may include links to Swagger UI or Scalar, databases may link to pgAdmin or similar management tools, and internal services may offer links to custom dashboards.</p>
<p>This setup allows users to test APIs or verify database connections directly from the dashboard without needing to remember specific ports or manually construct URLs.</p>
<h3 id="heading-real-time-logs"><strong>Real-Time Logs</strong></h3>
<p>Clicking into a specific service opens a detailed view showing real-time logs streamed directly from that service.</p>
<p>This is especially helpful when debugging startup issues or service interactions. Instead of running <code>dotnet run</code> in separate terminals, you can view logs for all your services in one place, color-coded and timestamped for clarity.</p>
<h3 id="heading-observability-built-in-opentelemetry"><strong>Observability Built-In (OpenTelemetry)</strong></h3>
<p>Aspire includes OpenTelemetry by default, which means that even without additional configuration, you automatically gain access to several powerful observability features. These include distributed traces across service boundaries, metrics for performance monitoring, and correlated logs that help track requests spanning multiple services.</p>
<p>For teams already using tools like Grafana, Jaeger, or SigNoz, Aspire can export this telemetry data to your preferred observability platform with minimal setup.</p>
<p>With tracing enabled, you can follow a request as it travels from your API to your database, through background workers, and back, all from within the dashboard.</p>
<h3 id="heading-why-the-dashboard-improves-developer-experience"><strong>Why the Dashboard Improves Developer Experience</strong></h3>
<p>Without Aspire, running a local microservices environment typically requires managing multiple terminal windows, tracking ports manually, and searching through log files to diagnose failures.</p>
<p>Aspire consolidates these tasks into a single visual interface where developers can view all services, check dependencies, inspect logs, and monitor system health directly from the browser.</p>
<p>This integrated environment enables faster debugging, maintains developer focus, and simplifies work with complex systems by reducing the overhead of manual coordination.</p>
<h2 id="heading-practical-scenarios-solving-real-world-dx-challenges-with-net-aspire"><strong>Practical Scenarios: Solving Real-World DX Challenges with .NET Aspire</strong></h2>
<p>So far, we have looked at how Aspire works and what it provides out of the box. But to really understand its impact on developer experience, let’s go through a few real-world pain points that almost every team building with microservices has faced, and how Aspire helps solve them.</p>
<h3 id="heading-starting-multiple-services-in-the-right-order"><strong>Starting Multiple Services in the Right Order</strong></h3>
<p><strong>The Problem:</strong> In most microservices setups, service startup order matters. For instance, your API Gateway might depend on the User Service and Catalog Service, which both depend on a Database.<br>If you start these in the wrong order, the gateway fails to connect, and you end up restarting services manually until everything stabilizes.</p>
<p><strong>How Aspire Solves It:</strong> Aspire provides a simple way to express dependencies using <code>.WaitFor()</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> builder = DistributedApplication.CreateBuilder(args);

<span class="hljs-keyword">var</span> db = builder.AddPostgres(<span class="hljs-string">"postgres"</span>);
<span class="hljs-keyword">var</span> user = builder.AddProject&lt;Projects.UserService&gt;(<span class="hljs-string">"user"</span>)
                  .WithReference(db);

<span class="hljs-keyword">var</span> catalog = builder.AddProject&lt;Projects.CatalogService&gt;(<span class="hljs-string">"catalog"</span>)
                     .WithReference(db);

<span class="hljs-keyword">var</span> gateway = builder.AddProject&lt;Projects.ApiGateway&gt;(<span class="hljs-string">"gateway"</span>)
                     .WaitFor(user)
                     .WaitFor(catalog);

builder.Build().Run();
</code></pre>
<p>Aspire automatically ensures that each service only starts after the services it depends on are fully ready.<br>No more manual sequencing or “start this one first” instructions in your <code>README</code>.</p>
<h3 id="heading-port-conflicts-and-configuration-drift"><strong>Port Conflicts and Configuration Drift</strong></h3>
<p><strong>The Problem:</strong> Developers often encounter the dreaded “Port 5000 is already in use” or spend time editing configuration files to avoid conflicts. Over time, local setups diverge across the team, making onboarding and debugging harder.</p>
<p><strong>How Aspire Solves It:</strong> Aspire dynamically manages ports and configuration at runtime. Each service gets a unique port assignment, and Aspire automatically shares connection information across services.</p>
<p>You can still set explicit ports when needed:</p>
<pre><code class="lang-csharp">builder.AddProject&lt;Projects.Frontend&gt;(<span class="hljs-string">"frontend"</span>)
       .WithHttpEndpoint(port: <span class="hljs-number">5173</span>);
</code></pre>
<p>This removes guesswork, keeps environments consistent, and ensures new developers can clone the repo and start everything without editing config files.</p>
<h3 id="heading-simplifying-new-developer-onboarding"><strong>Simplifying New Developer Onboarding</strong></h3>
<p><strong>The Problem:</strong> For many teams, onboarding means following a long README with dozens of setup steps, manual database migrations, and environment variable configurations. It can take hours, or even days before a new developer can run the system locally.</p>
<p><strong>How Aspire Solves It:</strong> Aspire defines your entire environment in code. That means the setup process becomes as simple as cloning the repository and running one command:</p>
<pre><code class="lang-plaintext">dotnet run
</code></pre>
<p>Aspire will start all necessary services, configure dependencies, and bring up the dashboard for visibility. This transforms onboarding from a multi-hour process into something that can be completed in minutes, with far fewer setup issues.</p>
<h3 id="heading-improving-debugging-and-cross-service-visibility"><strong>Improving Debugging and Cross-Service Visibility</strong></h3>
<p><strong>The Problem:</strong> Debugging in microservices often means jumping between logs, tracing requests across multiple services, or reproducing issues that only appear when several services run together.</p>
<p><strong>How Aspire Solves It:</strong> With built-in observability and the Aspire dashboard, you can view logs across all services in one place, inspect health checks and metrics, and trace requests using OpenTelemetry. This makes it much easier to identify issues across service boundaries and speeds up debugging, especially during integration testing or local development.</p>
<h3 id="heading-running-optional-or-external-services"><strong>Running Optional or External Services</strong></h3>
<p><strong>The Problem:</strong> Sometimes you don’t need to run every service locally. For example, you might connect to a shared staging API or external dependency instead of running a local instance.</p>
<p><strong>How Aspire Solves It:</strong> Aspire lets you make services optional using conditional checks:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">if</span> (Directory.Exists(<span class="hljs-string">"../Frontend"</span>))
{
    builder.AddProject&lt;Projects.Frontend&gt;(<span class="hljs-string">"frontend"</span>);
}
</code></pre>
<p>This makes your setup flexible: you can run a minimal environment for development or a full environment for integration testing, all using the same configuration.</p>
<h3 id="heading-why-these-scenarios-matter"><strong>Why These Scenarios Matter</strong></h3>
<p>Each of these examples solves a specific friction point in the developer experience. Startup complexity, environment drift, onboarding time, and debugging difficulty.</p>
<p>By automating orchestration and configuration, Aspire frees developers from repetitive setup work and lets them focus on building features instead of managing infrastructure.</p>
<h2 id="heading-going-further"><strong>Going Further</strong></h2>
<p>Once you’re comfortable with Aspire’s basics, you can extend it beyond local orchestration to streamline other parts of your workflow.</p>
<ul>
<li><p><strong>Integrate front-end applications</strong><br>  Orchestrate React, Angular, or Node.js apps alongside your .NET services for a unified full-stack setup.</p>
</li>
<li><p><strong>Export telemetry data</strong><br>  Send Aspire’s OpenTelemetry output to platforms like Grafana, Jaeger, or Azure Application Insights for deeper analysis.</p>
</li>
<li><p><strong>Use Aspire in CI/CD pipelines</strong><br>  Bring up full environments for integration or smoke testing during continuous integration runs, all using your existing Aspire configuration.</p>
</li>
<li><p><strong>Explore community examples</strong><br>  Check out the official Aspire samples and templates for advanced orchestration patterns, cloud integration, and observability setups.</p>
</li>
</ul>
<h2 id="heading-key-takeaways-and-when-to-use-net-aspire"><strong>Key Takeaways and When to Use .NET Aspire</strong></h2>
<p>As we’ve seen throughout this guide, .NET Aspire isn’t just another developer tool, it’s a framework built specifically to improve developer experience in microservices-based applications.</p>
<p>By orchestrating all your services in a consistent, declarative way, Aspire helps teams reduce friction, speed up setup, and make local environments more reliable and observable.</p>
<p><strong>Key Takeaways</strong></p>
<ol>
<li><p><strong>Developer Experience (DX) matters as your system grows.</strong><br> Microservices introduce flexibility and scalability, but they also add complexity; multiple services, ports, dependencies, and startup sequences. Without good orchestration, DX quickly degrades.</p>
</li>
<li><p><strong>Aspire simplifies orchestration for local development.</strong><br> It automatically handles service startup, dependencies, configuration sharing, and observability all defined in code, right within your .NET solution.</p>
</li>
<li><p><strong>The Aspire dashboard improves visibility.</strong><br> You get a centralized, real-time view of your entire system; services, logs, health, and endpoints eliminating the need for multiple terminals or manual tracking.</p>
</li>
<li><p><strong>Onboarding new developers becomes faster and smoother.</strong><br> A single <code>dotnet run</code> command can spin up your entire development environment, reducing setup time from hours or days to minutes.</p>
</li>
<li><p><strong>Built-in observability means better debugging and confidence.</strong><br> With OpenTelemetry integrated out of the box, developers can trace requests, monitor performance, and diagnose issues across services with minimal setup.</p>
</li>
</ol>
<h2 id="heading-when-and-when-not-to-use-net-aspire"><strong>When (and When Not) to Use .NET Aspire</strong></h2>
<p><strong>Use Aspire when:</strong></p>
<p>Aspire makes sense if you're building .NET microservices and tired of complex local setup. It's especially valuable when your team is dealing with environment drift, slow onboarding, or startup sequences that feel like juggling. If you want one command to spin up your entire system, with observability built in from day one, Aspire is worth trying.</p>
<p><strong>You might not need Aspire when:</strong></p>
<p>Aspire might not be worth it if your current setup already works well. Maybe you're using Kubernetes or Docker Compose locally and everything runs smoothly. Or you're building a monolith or single service that doesn't need orchestration. Or your stack has a lot of non-.NET components that would need custom wiring. If your local development is already simple and stable, don't fix what isn't broken.</p>
<p>In other words:<br>Aspire shines in the local development and onboarding phase. Helping developers build, test, and iterate on distributed systems with minimal friction.<br>It’s not meant to replace production orchestrators like Kubernetes but to complement them by improving the developer’s day-to-day workflow.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Developer Experience is often overlooked when teams move to microservices, but it directly impacts productivity, quality, and morale. By using <strong>.NET Aspire</strong>, you can bring order, visibility, and simplicity back to your local development environment.</p>
<p>If you’re looking to streamline your microservices workflow, give Aspire a try. You’ll spend less time fighting your setup and more time building what actually matters; great software.</p>
<p>Ready to get started? Check out the official <a target="_blank" href="https://learn.microsoft.com/dotnet/aspire/">.NET Aspire documentation</a> or clone one of the <a target="_blank" href="https://github.com/dotnet/aspire-samples">sample projects</a> to see it in action.</p>
<p>If you made it to the end of this tutorial, thanks for reading! You can also connect with me on <a target="_blank" href="https://www.linkedin.com/in/emidowojo/">LinkedIn</a> if you’d like to stay in touch.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Master REST API Development with .NET 9 ]]>
                </title>
                <description>
                    <![CDATA[ Building robust web applications and services requires a solid understanding of REST APIs, the backbone of modern web communication. Whether you're looking to create mobile app backends, integrate different systems, or build scalable web services, RE... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/master-rest-api-development-with-net-9/</link>
                <guid isPermaLink="false">6837137d733fa231f684d399</guid>
                
                    <category>
                        <![CDATA[ dotnet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 28 May 2025 13:45:33 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748439915268/7c83881c-ce95-4a13-86eb-3d3631e01b6c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building robust web applications and services requires a solid understanding of REST APIs, the backbone of modern web communication. Whether you're looking to create mobile app backends, integrate different systems, or build scalable web services, REST API development skills are essential for any aspiring developer. With .NET 9 and <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core providing powerful, industry-standard tools for API development, there's never been a better time to master these fundamental skills.</p>
<p>We've just published a comprehensive beginner-friendly course on the <a target="_blank" href="http://freeCodeCamp.org">freeCodeCamp.org</a> YouTube channel, taught by Alen Omeri, that takes you from absolute beginner to confidently building REST APIs in .NET 9. This step-by-step tutorial is specifically designed for those new to <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core, covering everything from basic REST concepts to implementing full CRUD operations with SQL Server database integration.</p>
<h2 id="heading-foundation-and-core-concepts">Foundation and Core Concepts</h2>
<p>The course begins with essential theoretical knowledge, explaining what REST APIs are and why they're crucial in modern software development. You'll understand the principles behind RESTful architecture and how APIs enable different applications to communicate effectively. This foundation ensures you're not just copying code, but truly understanding the concepts behind what you're building.</p>
<p>From there, you'll dive into getting started with <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core, learning the framework's structure and how it facilitates API development. The tutorial covers REST API models, teaching you how to design and structure your data representations properly. You'll then progress to creating controllers, the components that handle incoming requests and coordinate responses.</p>
<h2 id="heading-practical-implementation">Practical Implementation</h2>
<p>The hands-on portion begins with creating lists and working with data, giving you immediate practical experience with API functionality. You'll explore HTTP methods in detail, understanding how GET, POST, PUT, and DELETE operations work within the REST framework and how to implement each one effectively in your <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core application.</p>
<p>Database integration forms a crucial part of the course. You'll learn to install and configure SQL Server and SQL Server Management Studio (SSMS), essential tools for professional API development. The tutorial guides you through creating a database from scratch and establishing the connection between your API project and the database, skills that are fundamental for real-world applications.</p>
<h2 id="heading-advanced-database-operations">Advanced Database Operations</h2>
<p>The course covers data seeding techniques, showing you how to populate your database with initial data for testing and development purposes. This practical skill ensures your APIs have realistic data to work with during development and testing phases.</p>
<p>The culmination of the course focuses on implementing complete CRUD operations. You'll build Create, Read, Update, and Delete functionality that connects your API endpoints to actual database operations. This section ties together everything you've learned, demonstrating how to build a fully functional API that can handle real-world data management tasks.</p>
<h2 id="heading-why-this-course-matters">Why This Course Matters</h2>
<p>REST APIs are the foundation of virtually every modern web application, mobile app, and cloud service. Learning to build them properly with .NET 9 and <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core gives you access to Microsoft's robust, enterprise-grade development ecosystem. The skills you'll gain are directly applicable to professional software development roles and provide a solid foundation for more advanced topics like microservices architecture and cloud deployment.</p>
<p>The course's beginner-friendly approach ensures that even those with no prior API development experience can follow along and build confidence. By the end, you'll have created a complete, database-connected REST API and understand the principles needed to build more complex systems.</p>
<p>Ready to begin your journey into professional API development? Watch the full course on the <a target="_blank" href="https://youtu.be/38GNKtclDdE">freeCodeCamp.org YouTube channel</a> (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/38GNKtclDdE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The C# Class Handbook – Types of Classes with Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ Classes are the fundamental building blocks of object-oriented programming in C#. They allow you to create reusable and modular code by grouping related data and functions. Different types of classes serve various purposes. For instance, organizing y... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/classes-in-c-sharp-handbook-with-examples/</link>
                <guid isPermaLink="false">67659959cfcbe6eba5ecf6dc</guid>
                
                    <category>
                        <![CDATA[ C# ]]>
                    </category>
                
                    <category>
                        <![CDATA[ dotnet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ classes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Isaiah Clifford Opoku ]]>
                </dc:creator>
                <pubDate>Fri, 20 Dec 2024 16:20:41 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734711601436/b4de90be-1d93-4d8d-a4ed-ae09b192ef5c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Classes are the fundamental building blocks of object-oriented programming in C#. They allow you to create reusable and modular code by grouping related data and functions.</p>
<p>Different types of classes serve various purposes. For instance, organizing your logic to make your code easier to navigate is helpful when building an application.</p>
<p>You can group or separate your code into classes, and through inheritance, you can utilize different classes as needed. Classes help encapsulate your code, enabling you to reuse your logic in other application parts. Classes have many functionalities, and we will explore some of them in detail.</p>
<p>In this guide, we'll explore various types of classes in C# and how you can use them to create efficient and maintainable code.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before we proceed, you should have the following:</p>
<ol>
<li><p><strong>Basic knowledge of C#</strong>: you should understand C# syntax and basic programming constructs like variables, loops, and conditionals.</p>
</li>
<li><p><strong>Familiarity with Object-Oriented Programming (OOP) concepts</strong>: you should know how to work with classes, objects, inheritance, polymorphism, encapsulation, and abstraction.</p>
</li>
<li><p><strong>Familiarity with access modifiers</strong>: you should understand public, private, internal, and protected access modifiers.</p>
</li>
<li><p><strong>Experience with C# IDE/Environment</strong>: you should be able to write and run C# programs using an IDE like Visual Studio.</p>
</li>
</ol>
<p>If you want to learn more about C#, you can check out my YouTube channel: <a target="_blank" href="https://www.youtube.com/@CliffTech">CliffTech</a>.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-static-classes-in-c-sharp">Static Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sealed-classes-in-c-sharp">Sealed Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-concrete-classes-in-c-sharp">Concrete Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-abstract-classes-in-c-sharp">Abstract Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-singleton-classes-in-c-sharp">Singleton Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-generic-classes-in-c-sharp">Generic Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-internal-classes-in-c-sharp">Internal Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-nested-classes-in-c-sharp">Nested Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-partial-classes-in-c-sharp">Partial Classes in C Sharp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<p>The first type of class we’ll discuss is the Static Class. Let’s dive in.</p>
<h2 id="heading-static-classes-in-c-sharp">Static Classes in C Sharp</h2>
<p>Static classes are a special type of class in C# designed to provide a collection of related utility methods and properties that do not rely on instance data.</p>
<p>Static classes in C# are a unique type of class designed to house a collection of related utility methods and properties that don't depend on instance data.</p>
<p>Unlike regular classes, static classes cannot be instantiated, and they exclusively contain static members. This characteristic means they cannot be inherited, making them perfect for organizing stateless methods that don't require the features of object-oriented programming.</p>
<p>In essence, when we refer to stateless grouping, it implies that there's no need to create an instance to call a static method – you can simply use the class or method name directly. This approach provides a clear and efficient way to manage utility functions, enhancing code organization and accessibility.</p>
<h3 id="heading-example-of-a-static-class-in-c">Example of a Static Class in C</h3>
<p>Here's an example of a static class in C#:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">StaticClasses</span>
{
<span class="hljs-comment">// Define a static class</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MathUtils</span>
    {
        <span class="hljs-comment">// Static method to add two numbers</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">Add</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b</span>)</span>
        {
            <span class="hljs-keyword">return</span> a + b;
        }

        <span class="hljs-comment">// Static method to subtract two numbers</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">Subtract</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b</span>)</span>
        {
            <span class="hljs-keyword">return</span> a - b;
        }

       <span class="hljs-comment">// Static method to multiply two numbers</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">Multiply</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> a, <span class="hljs-keyword">int</span> b</span>)</span>
        {
            <span class="hljs-keyword">return</span> a * b;
        }
    }
}
</code></pre>
<p>In this example:</p>
<ul>
<li><p>The <code>MathUtils</code> class is defined as <code>static</code>, meaning it cannot be instantiated.</p>
</li>
<li><p>It contains three static methods: <code>Add</code>, <code>Subtract</code>, and <code>Multiply</code>.</p>
</li>
<li><p>These methods can be called directly on the <code>MathUtils</code> class without creating an instance.</p>
</li>
</ul>
<p>Before you can use this, you need to call it in your <code>Program.cs</code>. When you create any C# application, the entry point is <code>Program.cs</code>. You’ll need to go there and make sure to call these classes so that you can execute them. This is what we will be doing for the rest of the section.</p>
<h3 id="heading-how-to-use-static-methods-in-programcs">How to Use Static Methods in <code>Program.cs</code></h3>
<p>Now you can use the static methods defined in the <code>MathUtils</code> class as follows:</p>
<pre><code class="lang-csharp">
 <span class="hljs-comment">// program.cs</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">StaticClasses</span>
{
    <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
        {
             <span class="hljs-comment">// Call static methods from the MathUtils class</span>
            <span class="hljs-keyword">int</span> sum = MathUtils.Add(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>);
            <span class="hljs-comment">// Call the static method Subtract from the MathUtils class</span>
            <span class="hljs-keyword">int</span> difference = MathUtils.Subtract(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>);

            <span class="hljs-comment">// Call the static method Multiply from the MathUtils class</span>
            <span class="hljs-keyword">int</span> product = MathUtils.Multiply(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>);

            <span class="hljs-comment">// Display the results of the sum method</span>
            Console.WriteLine(<span class="hljs-string">$"Sum: <span class="hljs-subst">{sum}</span>"</span>); <span class="hljs-comment">// Output: 8</span>
            <span class="hljs-comment">// Display the results of the difference method</span>
            Console.WriteLine(<span class="hljs-string">$"Difference: <span class="hljs-subst">{difference}</span>"</span>); <span class="hljs-comment">// Output: 2</span>
            <span class="hljs-comment">// Display the results of the product method</span>
            Console.WriteLine(<span class="hljs-string">$"Product: <span class="hljs-subst">{product}</span>"</span>);  <span class="hljs-comment">// Output: 15</span>
        }
    }
}
</code></pre>
<h3 id="heading-when-to-use-statice-classes-vs-methods">When to Use Statice Classes vs Methods</h3>
<p>To decide when to use static classes or methods in C#, consider the following guidelines:</p>
<ol>
<li><p><strong>Use Static Classes when:</strong></p>
<ul>
<li><p>You need a collection of utility or helper methods that do not require any instance data.</p>
</li>
<li><p>The methods and properties are stateless and can be accessed globally without creating an object.</p>
</li>
<li><p>You want to group related functions that are not tied to a specific object state.</p>
</li>
<li><p>You need to ensure that the class cannot be instantiated or inherited.</p>
</li>
</ul>
</li>
<li><p><strong>Use Static Methods when:</strong></p>
<ul>
<li><p>You have a class that is mostly instance-based, but you need a few methods that do not depend on instance data.</p>
</li>
<li><p>The method performs a task that is independent of any object state and can be executed without an instance.</p>
</li>
<li><p>You want to provide a utility function within a class that can be accessed without creating an object of that class.</p>
</li>
</ul>
</li>
</ol>
<p>By using static classes and methods appropriately, you can enhance code organization, improve performance by avoiding unnecessary object creation, and ensure that certain functionalities are easily accessible throughout your application.</p>
<h3 id="heading-key-points-to-remember-about-static-classes-in-c">Key Points to Remember About Static Classes in C</h3>
<ul>
<li><p><strong>Cannot be Instantiated</strong>: You cannot create objects from a static class.</p>
</li>
<li><p><strong>Only static members</strong>: Static classes can only have static members. They do not support instance methods or fields.</p>
</li>
<li><p><strong>Sealed by default</strong>: Static classes are automatically sealed, so they cannot be inherited.</p>
</li>
<li><p><strong>Utility and helper methods</strong>: Static classes are usually used to group related utility or helper methods that don't need an object state.</p>
</li>
</ul>
<p>Static classes help organize and access utility methods and properties clearly and simply, making them important for creating efficient and maintainable code.</p>
<h2 id="heading-sealed-classes-in-c-sharp">Sealed Classes in C Sharp</h2>
<p>Sealed classes are a special type of class in C# that cannot be inherited. You can use them to prevent other classes from deriving from them, which can be useful for creating immutable types or ensuring that a class's behavior remains unchanged.</p>
<p>By sealing a class, you ensure that it cannot be modified or extended, making it useful for scenarios where you want to provide a specific implementation without allowing further alterations.</p>
<h3 id="heading-example-of-a-sealed-class-in-c">Example of a Sealed Class in C</h3>
<p>Here's an example of a sealed class in C#:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">SealedClasses</span>
{

    <span class="hljs-comment">// Define an abstract class</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Shape</span>
    {
        <span class="hljs-comment">// Abstract method to calculate the area</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateArea</span>(<span class="hljs-params"></span>)</span>;
    }

     <span class="hljs-comment">// Define a sealed class</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Rectangle</span> : <span class="hljs-title">Shape</span>
    {

        <span class="hljs-comment">//  Properties</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Width { <span class="hljs-keyword">get</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> Height { <span class="hljs-keyword">get</span>; }

        <span class="hljs-comment">// Constructor</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Rectangle</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> width, <span class="hljs-keyword">double</span> height</span>)</span>
        {
            Width = width;
            Height = height;
        }

       <span class="hljs-comment">// Implement the CalculateArea method</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateArea</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">return</span> Width * Height;
        }
    }
}
</code></pre>
<p>In this example:</p>
<ul>
<li><p>The <code>Shape</code> class is an abstract base class with an abstract method <code>CalculateArea()</code>.</p>
</li>
<li><p>The <code>Rectangle</code> class inherits from <code>Shape</code> and provides an implementation for <code>CalculateArea()</code>.</p>
</li>
<li><p>The <code>Rectangle</code> class is sealed, which means it cannot be inherited from. This ensures that the class's implementation cannot be modified or extended.</p>
</li>
</ul>
<h3 id="heading-how-to-use-the-sealed-rectangle-class-in-the-programcs">How to Use the Sealed Rectangle Class in the Program.cs</h3>
<p>Here's how you can use the <code>Rectangle</code> class in a <code>Program.cs</code> file:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">SealedClasses</span>
{
    <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
        {
            Rectangle rectangle = <span class="hljs-keyword">new</span> Rectangle(<span class="hljs-number">5</span>, <span class="hljs-number">3</span>);
            <span class="hljs-keyword">double</span> area = rectangle.CalculateArea();

            Console.WriteLine(<span class="hljs-string">$"Area of the rectangle: <span class="hljs-subst">{area}</span>"</span>); <span class="hljs-comment">// Output: Area of the rectangle: 15</span>
        }
    }
}
</code></pre>
<p>In this example, the <code>Rectangle</code> class is sealed to ensure that its behavior cannot be changed through inheritance. This guarantees that the <code>Rectangle</code> class's implementation of <code>CalculateArea()</code> stays the same, which helps maintain consistent behavior.</p>
<h3 id="heading-when-to-use-sealed-classes">When to Use Sealed Classes</h3>
<p>Sealed classes are particularly useful in the following contexts:</p>
<ol>
<li><p><strong>Framework development</strong>: When developing frameworks or libraries, you might use sealed classes to lock down certain classes that are not intended to be extended by users. This helps maintain control over the framework's behavior and ensures that users cannot introduce bugs or inconsistencies by extending these classes.</p>
</li>
<li><p><strong>Preventing inheritance</strong>: If a class is designed to be a specific implementation with no need for further customization or extension, sealing it prevents other developers from creating subclasses that might alter its intended functionality.</p>
</li>
<li><p><strong>Finalizing class design</strong>: When a class has reached a point where its design is considered complete and no further changes or extensions are anticipated, sealing it can signal to other developers that the class should be used as-is.</p>
</li>
<li><p><strong>Avoiding overriding</strong>: In scenarios where overriding methods could lead to incorrect behavior or security issues, sealing the class ensures that its methods cannot be overridden, preserving the original logic and functionality.</p>
</li>
</ol>
<h3 id="heading-key-points-to-remember-about-sealed-classes">Key Points to Remember About Sealed Classes</h3>
<ul>
<li><p><strong>No inheritance</strong>: Sealed classes cannot be inherited, ensuring their behavior stays the same.</p>
</li>
<li><p><strong>Prevent modification</strong>: They prevent further inheritance, avoiding accidental changes or extensions.</p>
</li>
<li><p><strong>Immutable and specific</strong>: Sealed classes are useful for creating immutable classes or when you need a specific, unchangeable implementation.</p>
</li>
</ul>
<h3 id="heading-sealed-classes-vs-static-classes">Sealed Classes vs. Static Classes</h3>
<p>You might wonder why we need sealed classes if static classes are already sealed. The key differences are:</p>
<ul>
<li><p><strong>Static Classes</strong> are sealed and cannot be instantiated. They are used for grouping static methods and properties.</p>
</li>
<li><p><strong>Sealed Classes</strong> can be instantiated but cannot be inherited. This allows for creating objects that are protected from further subclassing.</p>
</li>
</ul>
<p>Sealed classes offer flexibility in creating classes that can be used directly without the risk of modification through inheritance.</p>
<h2 id="heading-concrete-classes-in-c-sharp">Concrete Classes in C Sharp</h2>
<p>Concrete classes are essential in <code>object-oriented programming</code> in C#. They are fully implemented classes that you can use to create objects directly.</p>
<p>Unlike <code>abstract classes</code> or <code>interfaces</code>, concrete classes have complete implementations of all their methods and properties, making them versatile and fundamental to most C# applications.</p>
<p>A concrete class is not abstract. It includes full implementations of all its members—methods, properties, fields, and so on—and can be used to create objects. These classes represent real-world entities or concepts in your application, encapsulating both data (stored in fields or properties) and behavior (defined by methods).</p>
<h3 id="heading-example-defining-a-concrete-class-in-c">Example: Defining a Concrete Class in C</h3>
<p>Here's a simple example of a concrete class in C#:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Define a concrete class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Animal</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Speak</span>(<span class="hljs-params"></span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">"The animal makes a sound."</span>);
    }
}

<span class="hljs-comment">// Define a derived class that inherits from the Animal class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Dog</span> : <span class="hljs-title">Animal</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Bark</span>(<span class="hljs-params"></span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">"The dog barks."</span>);
    }
}
</code></pre>
<p>In this example, the <code>Animal</code> class is a concrete class with a method <code>Speak</code> that represents a generic sound made by any animal. The <code>Dog</code> class inherits from <code>Animal</code> and adds a <code>Bark</code> method to represent a sound specific to dogs. Both <code>Animal</code> and <code>Dog</code> are concrete classes because they can be instantiated and used to create objects.</p>
<h3 id="heading-how-to-instantiate-and-use-concrete-classes">How to Instantiate and Use Concrete Classes</h3>
<p>Here's how you can use the <code>Dog</code> class in a <code>Program.cs</code> file:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// program.cs</span>
<span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
    {
        <span class="hljs-comment">// Create an instance of the Dog class</span>
        Dog myDog = <span class="hljs-keyword">new</span> Dog();

        <span class="hljs-comment">// Call the inherited method</span>
        myDog.Speak(); <span class="hljs-comment">// Output: The animal makes a sound.</span>

        <span class="hljs-comment">// Call the method defined in the Dog class</span>
        myDog.Bark();  <span class="hljs-comment">// Output: The dog barks.</span>
    }
}
</code></pre>
<p>In this example, we create an instance of the <code>Dog</code> class called <code>myDog</code>. We first call the <code>Speak</code> method, which is inherited from the <code>Animal</code> class, and then the <code>Bark</code> method from the <code>Dog</code> class. This shows how concrete classes can include both inherited and unique behaviors.</p>
<h3 id="heading-real-world-example-concrete-class-for-a-product">Real-World Example: Concrete Class for a Product</h3>
<p>To illustrate the practical application of concrete classes, consider the following example of a <code>Product</code> class:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Define a concrete class for a product</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>
{
    <span class="hljs-comment">// Data properties</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-comment">// Method to display product information</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DisplayInfo</span>(<span class="hljs-params"></span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">$"Product: <span class="hljs-subst">{Name}</span>, Price: <span class="hljs-subst">{Price:C}</span>"</span>);
    }
}
</code></pre>
<p>This <code>Product</code> class is a concrete class with properties <code>Name</code> and <code>Price</code> to store information about a product. The <code>DisplayInfo</code> method provides a way to display the product’s details.</p>
<h4 id="heading-how-to-use-the-product-class">How to Use the <code>Product</code> Class</h4>
<p>Here's how you can use the <code>Product</code> class:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
    {
        <span class="hljs-comment">// Create an instance of the Product class</span>
        Product product = <span class="hljs-keyword">new</span> Product
        {
            Name = <span class="hljs-string">"Laptop"</span>,
            Price = <span class="hljs-number">1299.99</span>m
        };

        <span class="hljs-comment">// Display product information</span>
        product.DisplayInfo(); <span class="hljs-comment">// Output: Product: Laptop, Price: $1,299.99</span>
    }
}
</code></pre>
<p>In this scenario, the <code>Product</code> class is used to create a <code>product</code> object. The <code>DisplayInfo</code> method is called to show the product's name and price. This demonstrates how concrete classes are used to model and work with real-world data.</p>
<h3 id="heading-key-points-to-remember-about-concrete-classes">Key Points to Remember About Concrete Classes</h3>
<ul>
<li><p><strong>Instantiable</strong>: Concrete classes can be instantiated, allowing you to create objects that represent specific entities or concepts in your application.</p>
</li>
<li><p><strong>Complete implementation</strong>: Concrete classes provide full implementations of all methods and properties, unlike abstract classes or interfaces.</p>
</li>
<li><p><strong>Common use</strong>: They are the most common type of class in C#, used to define objects with specific behavior and data.</p>
</li>
</ul>
<p>Concrete classes are essential for C# development, enabling you to define and work with objects that model real-world entities within your applications. Understanding how to effectively use concrete classes is crucial for building robust, object-oriented software.</p>
<h2 id="heading-abstract-classes-in-c-sharp">Abstract Classes in C Sharp</h2>
<p>In C#, abstract classes are a powerful feature that allow you to define a blueprint for other classes without providing complete implementations. They serve as base classes that cannot be instantiated directly but can be inherited by other classes that will provide specific implementations for the abstract methods defined within them. This design helps enforce consistency across related classes while allowing flexibility in how certain behaviors are implemented.</p>
<h3 id="heading-what-does-instantiated-mean">What Does "Instantiated" Mean?</h3>
<p>Before exploring abstract classes, let's clarify what it means to instantiate a class. Instantiation is the process of creating an object from a class. When you use the <code>new</code> keyword in C#, you are creating an instance (or object) of that class.</p>
<p>But abstract classes cannot be instantiated directly. They must be inherited by a non-abstract (concrete) class that provides implementations for the abstract methods.</p>
<h3 id="heading-understanding-abstract-classes-and-abstract-methods">Understanding Abstract Classes and Abstract Methods</h3>
<p><strong>Abstract classes</strong> are classes you can't create objects from directly. They act as templates for other classes. They can have both complete methods and methods without a body (abstract methods). Abstract classes help set up a common interface and shared behavior for related classes.</p>
<p><strong>Abstract methods</strong>, on the other hand, are methods in an abstract class that don't have a body. Any non-abstract class that inherits from the abstract class must provide a body for these methods. This ensures all subclasses have a consistent interface.</p>
<h3 id="heading-real-world-example-bank-account-management">Real-World Example: Bank Account Management</h3>
<p>Let's explore a real-world example to illustrate the concept of abstract classes and abstract methods in C#.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-comment">// define an abstract class</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">AbstractClasses</span>
{
    <span class="hljs-comment">// Abstract class</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BankAccount</span>
    {
        <span class="hljs-comment">// Properties</span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> AccountNumber { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Balance { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">protected</span> <span class="hljs-keyword">set</span>; }

        <span class="hljs-comment">// Constructor</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BankAccount</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> accountNumber, <span class="hljs-keyword">decimal</span> initialBalance</span>)</span>
        {
            AccountNumber = accountNumber;
            Balance = initialBalance;
        }

        <span class="hljs-comment">// Abstract methods</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Deposit</span>(<span class="hljs-params"><span class="hljs-keyword">decimal</span> amount</span>)</span>;
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Withdraw</span>(<span class="hljs-params"><span class="hljs-keyword">decimal</span> amount</span>)</span>;
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DisplayAccountInfo</span>(<span class="hljs-params"></span>)</span>;
    }

    <span class="hljs-comment">// Derived class: SavingsAccount</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SavingsAccount</span> : <span class="hljs-title">BankAccount</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">decimal</span> interestRate;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">SavingsAccount</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> accountNumber, <span class="hljs-keyword">decimal</span> initialBalance, <span class="hljs-keyword">decimal</span> interestRate</span>)
            : <span class="hljs-title">base</span>(<span class="hljs-params">accountNumber, initialBalance</span>)</span>
        {
            <span class="hljs-keyword">this</span>.interestRate = interestRate;
        }

        <span class="hljs-comment">// Implementing abstract methods</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Deposit</span>(<span class="hljs-params"><span class="hljs-keyword">decimal</span> amount</span>)</span>
        {
            Balance += amount;
            Console.WriteLine(<span class="hljs-string">$"Deposited <span class="hljs-subst">{amount}</span> to Savings Account <span class="hljs-subst">{AccountNumber}</span>. New Balance: <span class="hljs-subst">{Balance}</span>"</span>);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Withdraw</span>(<span class="hljs-params"><span class="hljs-keyword">decimal</span> amount</span>)</span>
        {
            <span class="hljs-keyword">if</span> (amount &gt; Balance)
            {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"Insufficient funds."</span>);
            }
            Balance -= amount;
            Console.WriteLine(<span class="hljs-string">$"Withdrew <span class="hljs-subst">{amount}</span> from Savings Account <span class="hljs-subst">{AccountNumber}</span>. New Balance: <span class="hljs-subst">{Balance}</span>"</span>);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DisplayAccountInfo</span>(<span class="hljs-params"></span>)</span>
        {
            Console.WriteLine(<span class="hljs-string">$"Savings Account <span class="hljs-subst">{AccountNumber}</span> - Balance: <span class="hljs-subst">{Balance}</span>, Interest Rate: <span class="hljs-subst">{interestRate}</span>%"</span>);
        }
    }

    <span class="hljs-comment">// Derived class: CheckingAccount</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CheckingAccount</span> : <span class="hljs-title">BankAccount</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">decimal</span> overdraftLimit;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CheckingAccount</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> accountNumber, <span class="hljs-keyword">decimal</span> initialBalance, <span class="hljs-keyword">decimal</span> overdraftLimit</span>)
            : <span class="hljs-title">base</span>(<span class="hljs-params">accountNumber, initialBalance</span>)</span>
        {
            <span class="hljs-keyword">this</span>.overdraftLimit = overdraftLimit;
        }

        <span class="hljs-comment">// Implementing abstract methods</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Deposit</span>(<span class="hljs-params"><span class="hljs-keyword">decimal</span> amount</span>)</span>
        {
            Balance += amount;
            Console.WriteLine(<span class="hljs-string">$"Deposited <span class="hljs-subst">{amount}</span> to Checking Account <span class="hljs-subst">{AccountNumber}</span>. New Balance: <span class="hljs-subst">{Balance}</span>"</span>);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Withdraw</span>(<span class="hljs-params"><span class="hljs-keyword">decimal</span> amount</span>)</span>
        {
            <span class="hljs-keyword">if</span> (amount &gt; Balance + overdraftLimit)
            {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"Overdraft limit exceeded."</span>);
            }
            Balance -= amount;
            Console.WriteLine(<span class="hljs-string">$"Withdrew <span class="hljs-subst">{amount}</span> from Checking Account <span class="hljs-subst">{AccountNumber}</span>. New Balance: <span class="hljs-subst">{Balance}</span>"</span>);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DisplayAccountInfo</span>(<span class="hljs-params"></span>)</span>
        {
            Console.WriteLine(<span class="hljs-string">$"Checking Account <span class="hljs-subst">{AccountNumber}</span> - Balance: <span class="hljs-subst">{Balance}</span>, Overdraft Limit: <span class="hljs-subst">{overdraftLimit}</span>"</span>);
        }
    }
}
</code></pre>
<p>In this example, the <code>BankAccount</code> class is an abstract class that defines a common interface for different types of bank accounts. It includes abstract methods like <code>Deposit</code>, <code>Withdraw</code>, and <code>DisplayAccountInfo</code>, which must be implemented by any class that inherits from <code>BankAccount</code>.</p>
<p>The <code>SavingsAccount</code> and <code>CheckingAccount</code> classes inherit from <code>BankAccount</code> and provide specific implementations for these abstract methods. This design enforces that every type of bank account must implement deposit, withdrawal, and display functions, while still allowing each account type to implement these functions in a way that makes sense for that specific type.</p>
<h3 id="heading-how-to-use-abstract-classes-in-a-program">How to Use Abstract Classes in a Program</h3>
<p>Let's see how we can use the <code>SavingsAccount</code> and <code>CheckingAccount</code> classes in a <code>Program.cs</code> file.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">AbstractClasses</span>
{
    <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
        {
            <span class="hljs-comment">// Create a savings account</span>
            BankAccount savings = <span class="hljs-keyword">new</span> SavingsAccount(<span class="hljs-string">"SA123"</span>, <span class="hljs-number">1000</span>, <span class="hljs-number">1.5</span>m);
            <span class="hljs-comment">// Create a checking account</span>
            BankAccount checking = <span class="hljs-keyword">new</span> CheckingAccount(<span class="hljs-string">"CA123"</span>, <span class="hljs-number">500</span>, <span class="hljs-number">200</span>);

            <span class="hljs-comment">// Deposit and withdraw from the savings account</span>
            savings.DisplayAccountInfo();

           <span class="hljs-comment">// Deposit and withdraw from the checking account</span>
            savings.Deposit(<span class="hljs-number">200</span>);

            savings.Withdraw(<span class="hljs-number">100</span>);
            <span class="hljs-comment">// Display the updated account information</span>
            savings.DisplayAccountInfo();

            checking.DisplayAccountInfo();

             <span class="hljs-comment">// Deposit and withdraw from the checking account</span>
            checking.Deposit(<span class="hljs-number">300</span>);

            checking.Withdraw(<span class="hljs-number">600</span>);

            <span class="hljs-comment">// Display the updated account information</span>
            checking.DisplayAccountInfo();

            <span class="hljs-keyword">try</span>
            {
                checking.Withdraw(<span class="hljs-number">200</span>);
            }
            <span class="hljs-keyword">catch</span> (InvalidOperationException ex)
            {
                Console.WriteLine(<span class="hljs-string">$"Error: <span class="hljs-subst">{ex.Message}</span>"</span>);
            }

            checking.DisplayAccountInfo();
        }
    }
}
</code></pre>
<p>This program will produce the following output:</p>
<pre><code class="lang-markdown">Savings Account SA123 - Balance: 1000, Interest Rate: 1.5%
Deposited 200 to Savings Account SA123. New Balance: 1200
Withdrew 100 from Savings Account SA123. New Balance: 1100
Savings Account SA123 - Balance: 1100, Interest Rate: 1.5%
Checking Account CA123 - Balance: 500, Overdraft Limit: 200
Deposited 300 to Checking Account CA123. New Balance: 800
Withdrew 600 from Checking Account CA123. New Balance: 200
Checking Account CA123 - Balance: 200, Overdraft Limit: 200
Withdrew 200 from Checking Account CA123. New Balance: 0
Checking Account CA123 - Balance: 0, Overdraft Limit: 200
</code></pre>
<p>In this example, the <code>SavingsAccount</code> and <code>CheckingAccount</code> objects are created, and the abstract methods <code>Deposit</code>, <code>Withdraw</code>, and <code>DisplayAccountInfo</code> are called. The abstract class <code>BankAccount</code> ensures that both account types have these methods, while the derived classes provide the specific functionality.</p>
<h3 id="heading-key-points-to-remember-about-abstract-classes">Key Points to Remember About Abstract Classes</h3>
<ul>
<li><p><strong>Cannot be instantiated</strong>: You can't create an instance of an abstract class directly. A subclass must inherit it and provide the implementations for the abstract methods.</p>
</li>
<li><p><strong>Contain abstract methods</strong>: Abstract methods in an abstract class have no body. Any non-abstract class that inherits from the abstract class must implement these methods.</p>
</li>
<li><p><strong>Define common interfaces</strong>: Abstract classes set a common interface for related classes, ensuring they are consistent while allowing different implementations.</p>
</li>
</ul>
<p>Abstract classes are important in C#. They help enforce a structure across related classes but still allow for specific details. By using abstract classes, you can make your code more organized, easier to maintain, and extend.</p>
<h2 id="heading-singleton-classes-in-c-sharp">Singleton Classes in C Sharp</h2>
<p>Singleton classes are a design pattern that restricts the instantiation of a class to one single instance. This is particularly useful when you need a single, shared resource across your application, such as a configuration manager, logging service, or database connection.</p>
<h3 id="heading-why-use-singleton-classes-in-c">Why Use Singleton Classes in C#?</h3>
<p>Imagine you have a class responsible for managing a database connection. You don’t want multiple instances of this class running around, potentially causing issues with resource management or inconsistent data. A Singleton class ensures that only one instance is created and provides a global point of access to it.</p>
<h3 id="heading-example-defining-a-singleton-class">Example: Defining a Singleton Class</h3>
<p>Let’s now see how you can implement a Singleton class in C#:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Define a singleton class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Singleton</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton instance;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">object</span> lockObject = <span class="hljs-keyword">new</span> <span class="hljs-keyword">object</span>();

    <span class="hljs-comment">// Private constructor prevents instantiation from outside the class</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">Singleton</span>(<span class="hljs-params"></span>)</span>
    {
    }

    <span class="hljs-comment">// Public property to access the single instance of the class</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton Instance
    {
        <span class="hljs-keyword">get</span>
        {
            <span class="hljs-comment">// Ensure thread safety</span>
            <span class="hljs-keyword">lock</span> (lockObject)
            {
                <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>)
                {
                    instance = <span class="hljs-keyword">new</span> Singleton();
                }
            }
            <span class="hljs-keyword">return</span> instance;
        }
    }

    <span class="hljs-comment">// Example method to demonstrate the singleton instance</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintMessage</span>(<span class="hljs-params"></span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">"Hello, I am a singleton class."</span>);
    }
}
</code></pre>
<p>In this example, the <code>Singleton</code> class is defined with a private constructor, which prevents other classes from creating new instances. The static property <code>Instance</code> returns the single instance of the class, creating it if it doesn't already exist. The <code>lockObject</code> ensures that the class is thread-safe, meaning that even in a multi-threaded environment, only one instance will be created.</p>
<p>The <code>PrintMessage</code> method is just a simple example to show that the Singleton instance can be used like any other class instance.</p>
<h3 id="heading-how-to-use-the-singleton-class-in-programcs">How to Use the Singleton Class in <code>Program.cs</code></h3>
<p>Now let’s see how you can use this Singleton class in your application:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
    {
        <span class="hljs-comment">// Retrieve the single instance of the Singleton class</span>
        Singleton singleton1 = Singleton.Instance;
        singleton1.PrintMessage(); <span class="hljs-comment">// Output: Hello, I am a singleton class.</span>

        <span class="hljs-comment">// Retrieve the instance again</span>
        Singleton singleton2 = Singleton.Instance;

        <span class="hljs-comment">// Check if both instances are the same</span>
        Console.WriteLine(singleton1 == singleton2); <span class="hljs-comment">// Output: True</span>
    }
}
</code></pre>
<p>In this example, we retrieve the Singleton instance twice. Because the class is a Singleton, both <code>singleton1</code> and <code>singleton2</code> refer to the same instance. The <code>==</code> operator confirms this by returning <code>true</code>.</p>
<h3 id="heading-how-to-extend-the-singleton-example">How to Extend the Singleton Example</h3>
<p>You can expand the Singleton pattern to handle more complex scenarios. For example, you could initialize the Singleton instance with configuration data:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ConfigurationManager</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ConfigurationManager instance;
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-title">Dictionary</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">string</span>&gt; settings</span> = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>&gt;();

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">ConfigurationManager</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// Simulate loading settings from a configuration file</span>
        settings[<span class="hljs-string">"AppName"</span>] = <span class="hljs-string">"MyApplication"</span>;
        settings[<span class="hljs-string">"Version"</span>] = <span class="hljs-string">"1.0.0"</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ConfigurationManager Instance
    {
        <span class="hljs-keyword">get</span>
        {
            <span class="hljs-keyword">if</span> (instance == <span class="hljs-literal">null</span>)
            {
                instance = <span class="hljs-keyword">new</span> ConfigurationManager();
            }
            <span class="hljs-keyword">return</span> instance;
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetSetting</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> key</span>)</span>
    {
        <span class="hljs-keyword">return</span> settings.ContainsKey(key) ? settings[key] : <span class="hljs-literal">null</span>;
    }
}
</code></pre>
<p>Here, <code>ConfigurationManager</code> is a Singleton class that loads and manages application settings. The <code>GetSetting</code> method allows you to retrieve specific configuration values, ensuring that all parts of your application use the same settings.</p>
<h3 id="heading-key-points-to-remember-about-singleton-classes">Key Points to Remember About Singleton Classes</h3>
<ul>
<li><p><strong>Single instance</strong>: Singleton classes ensure that only one instance of the class exists in the application.</p>
</li>
<li><p><strong>Global access</strong>: Singleton provides a global point of access to the instance, making it easy to use across different parts of your application.</p>
</li>
<li><p><strong>Thread safety</strong>: In multi-threaded environments, ensure your Singleton is thread-safe to avoid creating multiple instances.</p>
</li>
<li><p><strong>Use cases</strong>: Common use cases for Singleton include managing configurations, logging services, and database connections.</p>
</li>
</ul>
<p>Singleton classes are a fundamental design pattern in software engineering, offering a simple yet powerful way to manage shared resources. Understanding and correctly implementing Singletons can help you write more efficient and maintainable code.</p>
<h2 id="heading-generic-classes-in-c-sharp">Generic Classes in C Sharp</h2>
<p>Generic classes in C# provide a powerful way to create reusable and type-safe code. By using generic classes, you can design a single class that works with any data type, eliminating the need for type-specific implementations. This makes your code more flexible and reduces redundancy.</p>
<h3 id="heading-why-use-generic-classes">Why Use Generic Classes?</h3>
<p>Imagine you need to implement a stack that stores integers. Later, you might need another stack to store strings.</p>
<p>Instead of writing two separate classes, you can write one generic stack class that can handle both data types—and any others you might need. Generic classes help you avoid code duplication and make your codebase easier to maintain.</p>
<h3 id="heading-example-defining-a-generic-class">Example: Defining a Generic Class</h3>
<p>Let’s take a look at a simple implementation of a generic stack class:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Define a generic class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Stack</span>&lt;<span class="hljs-title">T</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">List</span>&lt;<span class="hljs-title">T</span>&gt; items</span> = <span class="hljs-keyword">new</span> List&lt;T&gt;();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Push</span>(<span class="hljs-params">T item</span>)</span>
    {
        items.Add(item);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> T <span class="hljs-title">Pop</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (items.Count == <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"The stack is empty."</span>);
        }
        T item = items[items.Count - <span class="hljs-number">1</span>];
        items.RemoveAt(items.Count - <span class="hljs-number">1</span>);
        <span class="hljs-keyword">return</span> item;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> T <span class="hljs-title">Peek</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (items.Count == <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"The stack is empty."</span>);
        }
        <span class="hljs-keyword">return</span> items[items.Count - <span class="hljs-number">1</span>];
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">IsEmpty</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> items.Count == <span class="hljs-number">0</span>;
    }
}
</code></pre>
<p>In this example, the <code>Stack&lt;T&gt;</code> class is defined with a type parameter <code>T</code>. This type parameter is a placeholder that represents the type of data the stack will store. The class includes methods like <code>Push</code> to add an item to the stack, <code>Pop</code> to remove and return the top item, <code>Peek</code> to view the top item without removing it, and <code>IsEmpty</code> to check if the stack is empty.</p>
<p>Because <code>Stack&lt;T&gt;</code> is generic, you can use it with any data type, whether it's <code>int</code>, <code>string</code>, or even a custom class.</p>
<h3 id="heading-how-to-use-the-stack-class-in-programcs">How to Use the Stack Class in <code>Program.cs</code></h3>
<p>Let’s see how this generic <code>Stack</code> class can be used in a program:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
    {
        <span class="hljs-comment">// Stack for integers</span>
        Stack&lt;<span class="hljs-keyword">int</span>&gt; intStack = <span class="hljs-keyword">new</span> Stack&lt;<span class="hljs-keyword">int</span>&gt;();
        intStack.Push(<span class="hljs-number">10</span>);
        intStack.Push(<span class="hljs-number">20</span>);
        Console.WriteLine(intStack.Pop()); <span class="hljs-comment">// Output: 20</span>
        Console.WriteLine(intStack.Peek()); <span class="hljs-comment">// Output: 10</span>

        <span class="hljs-comment">// Stack for strings</span>
        Stack&lt;<span class="hljs-keyword">string</span>&gt; stringStack = <span class="hljs-keyword">new</span> Stack&lt;<span class="hljs-keyword">string</span>&gt;();
        stringStack.Push(<span class="hljs-string">"Hello"</span>);
        stringStack.Push(<span class="hljs-string">"World"</span>);
        Console.WriteLine(stringStack.Pop()); <span class="hljs-comment">// Output: World</span>
        Console.WriteLine(stringStack.Peek()); <span class="hljs-comment">// Output: Hello</span>
    }
}
</code></pre>
<p>In this example, we create two instances of the <code>Stack</code> class: one that stores integers and another that stores strings. The flexibility of generics allows us to use the same class to work with different data types, making our code more reusable and concise.</p>
<h3 id="heading-how-to-extend-the-generic-class">How to Extend the Generic Class</h3>
<p>Let’s take it a step further and extend our <code>Stack</code> class to include a method that returns all items as an array:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> T[] <span class="hljs-title">ToArray</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">return</span> items.ToArray();
}
</code></pre>
<p>Now, you can easily convert the stack’s items into an array:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">int</span>[] intArray = intStack.ToArray();
<span class="hljs-keyword">string</span>[] stringArray = stringStack.ToArray();
</code></pre>
<p>This extension further showcases the power of generics, allowing the same method to work with different data types seamlessly.</p>
<h3 id="heading-key-points-to-remember-about-generic-classes">Key Points to Remember About Generic Classes</h3>
<ul>
<li><p><strong>Flexibility</strong>: Generic classes can handle any data type, making them adaptable and reusable.</p>
</li>
<li><p><strong>Type safety</strong>: Using type parameters ensures that your code is type-safe, catching errors during compile-time instead of runtime.</p>
</li>
<li><p><strong>Code reuse</strong>: Generics remove the need to duplicate code for different data types, resulting in cleaner and easier-to-maintain code.</p>
</li>
<li><p><strong>Type parameters</strong>: Generic classes use type parameters as placeholders for the actual data types you will use when creating an instance of the class.</p>
</li>
</ul>
<p>Generic classes are crucial in C# for building flexible, reusable, and type-safe code. By learning and using generics, you can create more reliable and maintainable applications.</p>
<h2 id="heading-internal-classes-in-c-sharp">Internal Classes in C Sharp</h2>
<p>Internal classes in C# are a powerful way to encapsulate implementation details within an assembly. By using the <code>internal</code> access modifier, you can restrict access to certain classes, ensuring they are only accessible within the same assembly.</p>
<p>This is particularly useful for hiding complex logic or utility classes that are not intended to be exposed to the public API of your library or application.</p>
<h3 id="heading-why-use-internal-classes">Why Use Internal Classes?</h3>
<p>In a large application, you may have classes that should only be used internally by your code and not by external consumers. For example, helper classes, utility functions, or components of a larger system that do not need to be exposed outside the assembly can be marked as <code>internal</code>. This ensures that your public API remains clean and focused while still allowing full functionality within the assembly.</p>
<h3 id="heading-example-defining-an-internal-class">Example: Defining an Internal Class</h3>
<p>Let’s consider a scenario where you have a library that processes orders. You might have a class that handles the complex logic of calculating discounts, but you don't want this class to be accessible to users of your library. Instead, you only expose the main <code>OrderProcessor</code> class, keeping the discount logic hidden with an internal class.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Define a public class that uses an internal class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OrderProcessor</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ProcessOrder</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> orderId</span>)</span>
    {
        <span class="hljs-comment">// Internal class is used here</span>
        DiscountCalculator calculator = <span class="hljs-keyword">new</span> DiscountCalculator();
        <span class="hljs-keyword">decimal</span> discount = calculator.CalculateDiscount(orderId);
        Console.WriteLine(<span class="hljs-string">$"Order <span class="hljs-subst">{orderId}</span> processed with a discount of <span class="hljs-subst">{discount:C}</span>"</span>);
    }

    <span class="hljs-comment">// Internal class that handles discount calculations</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DiscountCalculator</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> <span class="hljs-title">CalculateDiscount</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> orderId</span>)</span>
        {
            <span class="hljs-comment">// Complex discount calculation logic</span>
            <span class="hljs-keyword">return</span> orderId * <span class="hljs-number">0.05</span>m;
        }
    }
}
</code></pre>
<p>In this example, the <code>DiscountCalculator</code> class is marked as <code>internal</code>, meaning it’s only accessible within the assembly. The <code>OrderProcessor</code> class, which is <code>public</code>, uses this internal class to process orders. External users of the library can call <code>ProcessOrder</code> without needing to know about or interact with the <code>DiscountCalculator</code> class.</p>
<h3 id="heading-how-to-use-the-internal-class-in-programcs">How to Use the Internal Class in <code>Program.cs</code></h3>
<p>Now, let's see how this works in practice:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
    {
        OrderProcessor processor = <span class="hljs-keyword">new</span> OrderProcessor();
        processor.ProcessOrder(<span class="hljs-number">12345</span>); <span class="hljs-comment">// Output: Order 12345 processed with a discount of $617.25</span>
    }
}
</code></pre>
<p>In this example, the <code>ProcessOrder</code> method is publicly accessible, but the internal workings of discount calculation remain hidden, providing a clean and secure API.</p>
<h3 id="heading-key-points-to-remember-about-internal-classes">Key Points to Remember About Internal Classes</h3>
<ul>
<li><p><strong>Limited access</strong>: Internal classes can only be accessed within the same assembly, which helps keep your public API simple and focused.</p>
</li>
<li><p><strong>Encapsulation</strong>: They are used to hide implementation details, like helper functions or complex logic, that shouldn't be publicly visible.</p>
</li>
<li><p><strong>Visibility control</strong>: The <code>internal</code> access modifier lets you control which classes and members are visible, ensuring only the necessary parts of your code are accessible to other assemblies.</p>
</li>
</ul>
<p>Internal classes are important for managing complex applications, allowing you to control what parts of your code can be accessed from outside your assembly. By hiding details and limiting access, you can keep your codebase clean, easy to maintain, and secure.</p>
<h2 id="heading-nested-classes-in-c-sharp">Nested Classes in C Sharp</h2>
<p>Nested classes in C# are defined within another class. This structure is useful for grouping related classes together and encapsulating the implementation details. Nested classes can be either static or non-static, and they have direct access to the private members of their enclosing class.</p>
<h3 id="heading-why-use-nested-classes">Why Use Nested Classes?</h3>
<p>Nested classes are particularly useful when a class is closely tied to the logic of another class and isn’t meant to be used independently. They allow you to encapsulate helper classes, hide them from other parts of the program, and keep related code together. This can lead to a cleaner, more organized codebase.</p>
<h3 id="heading-example-defining-a-nested-class">Example: Defining a Nested Class</h3>
<p>Let’s consider a scenario where we have a class that represents a <code>Car</code> and another class that represents a <code>Engine</code>. Since the <code>Engine</code> class is closely related to the <code>Car</code> class and doesn’t make much sense on its own, we can define it as a nested class within <code>Car</code>.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Define a class with a nested class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Car</span>
{
    <span class="hljs-comment">// Define private fields</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> model;
    <span class="hljs-keyword">private</span> Engine carEngine;

   <span class="hljs-comment">// Constructor</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Car</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> model</span>)</span>
    {
        <span class="hljs-keyword">this</span>.model = model;
        carEngine = <span class="hljs-keyword">new</span> Engine();
    }


    <span class="hljs-comment">// Method to start the car</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StartCar</span>(<span class="hljs-params"></span>)</span>
    {
        carEngine.StartEngine();
        Console.WriteLine(<span class="hljs-string">$"<span class="hljs-subst">{model}</span> is starting..."</span>);
    }

    <span class="hljs-comment">// Nested class</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Engine</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">StartEngine</span>(<span class="hljs-params"></span>)</span>
        {
            Console.WriteLine(<span class="hljs-string">"Engine started."</span>);
        }
    }
}
</code></pre>
<p>In this example, the <code>Car</code> class has a private field <code>model</code> and a method <code>StartCar</code> that starts the car. The <code>Engine</code> class is nested within the <code>Car</code> class and contains a <code>StartEngine</code> method. By nesting <code>Engine</code> inside <code>Car</code>, we express the close relationship between the two.</p>
<h3 id="heading-how-to-use-the-nested-class-in-programcs">How to Use the Nested Class in <code>Program.cs</code></h3>
<p>Let’s see how we can use the <code>Car</code> class and its nested <code>Engine</code> class in a program:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
    {
        Car myCar = <span class="hljs-keyword">new</span> Car(<span class="hljs-string">"Toyota"</span>);
        myCar.StartCar(); <span class="hljs-comment">// Output: Engine started. Toyota is starting...</span>

        <span class="hljs-comment">// Although you can create an instance of the nested class separately, it usually makes sense to use it through the outer class</span>
        Car.Engine engine = <span class="hljs-keyword">new</span> Car.Engine();
        engine.StartEngine(); <span class="hljs-comment">// Output: Engine started.</span>
    }
}
</code></pre>
<p>In this example, we create an instance of the <code>Car</code> class and call the <code>StartCar</code> method, which internally calls the <code>StartEngine</code> method of the nested <code>Engine</code> class. While it's possible to instantiate the nested class separately, it’s more common to access it through the outer class, emphasizing the relationship between the two.</p>
<h3 id="heading-key-points-to-remember-about-nested-classes">Key Points to Remember About Nested Classes</h3>
<ul>
<li><p><strong>Encapsulation</strong>: Nested classes keep details hidden that shouldn't be seen outside the main class.</p>
</li>
<li><p><strong>Access to private members</strong>: Nested classes can access private parts of the main class, making them good for helper classes that need to work with the main class's internal parts.</p>
</li>
<li><p><strong>Organization</strong>: Use nested classes to keep related classes together, which makes the code cleaner and more organized.</p>
</li>
<li><p><strong>Static or non-static</strong>: Nested classes can be static or non-static. Static nested classes can't access the instance parts of the main class directly, but non-static nested classes can.</p>
</li>
</ul>
<p>Nested classes are a useful way to organize your code, especially for complex objects with closely related parts. Keeping related classes together makes your code easier to manage and maintain.</p>
<h2 id="heading-partial-classes-in-c-sharp">Partial Classes in C Sharp</h2>
<p>Partial classes in C# allow you to split a class definition across multiple files. This feature is particularly useful in large projects, where it can be beneficial to break a complex class into smaller, more manageable sections.</p>
<p>By using the <code>partial</code> keyword, you can organize your code better, especially when working with generated code or collaborating in a team environment.</p>
<h3 id="heading-why-use-partial-classes">Why Use Partial Classes?</h3>
<p>Imagine you’re working on a large application where a single class contains hundreds of lines of code. This can become difficult to manage and maintain. By using partial classes, you can divide the class into logical parts, each residing in a separate file. This not only makes the code more readable but also allows multiple developers to work on different parts of the class simultaneously without causing merge conflicts.</p>
<h3 id="heading-example-defining-a-partial-class-in-c">Example: Defining a Partial Class in C</h3>
<p>Let’s say we have a class that handles various operations for an employee management system. Instead of putting all methods in one file, we can split them across multiple files using partial classes.</p>
<p><strong>File 1:</strong> <code>PartialClass_Methods1.cs</code></p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Define a partial class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmployeeOperations</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddEmployee</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> name</span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">$"Employee <span class="hljs-subst">{name}</span> added."</span>);
    }
}
</code></pre>
<p><strong>File 2:</strong> <code>PartialClass_Methods2.cs</code></p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Define the other part of the partial class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmployeeOperations</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">RemoveEmployee</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> name</span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">$"Employee <span class="hljs-subst">{name}</span> removed."</span>);
    }
}
</code></pre>
<p>In these examples, the <code>EmployeeOperations</code> class is split into two files, each containing a part of the class. The first file handles adding employees, while the second file handles removing them.</p>
<h3 id="heading-how-to-use-the-partial-class-in-programcs">How to Use the Partial Class in <code>Program.cs</code></h3>
<p>Now, let’s use the <code>EmployeeOperations</code> class in our <code>Program.cs</code> file:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
    {
        EmployeeOperations operations = <span class="hljs-keyword">new</span> EmployeeOperations();

        operations.AddEmployee(<span class="hljs-string">"John Doe"</span>);    <span class="hljs-comment">// Output: Employee John Doe added.</span>
        operations.RemoveEmployee(<span class="hljs-string">"John Doe"</span>); <span class="hljs-comment">// Output: Employee John Doe removed.</span>
    }
}
</code></pre>
<p>In this example, the <code>EmployeeOperations</code> class, although defined in multiple files, behaves like a single class. The methods <code>AddEmployee</code> and <code>RemoveEmployee</code> are seamlessly combined, providing a clean and organized way to manage operations.</p>
<h3 id="heading-key-points-to-remember-about-partial-classes">Key Points to Remember About Partial Classes</h3>
<ul>
<li><p><strong>Code organization</strong>: Partial classes help keep large classes organized by splitting them into smaller, focused sections.</p>
</li>
<li><p><strong>Team collaboration</strong>: Multiple developers can work on different parts of the same class without interfering with each other’s code.</p>
</li>
<li><p><strong>Generated code</strong>: Often used with auto-generated code, where part of the class is generated by a tool, and the rest is written manually.</p>
</li>
</ul>
<p>Partial classes are a powerful feature in C# that allows for better code management, especially in large-scale applications. By breaking down a class into logical components, you can maintain clean, readable, and maintainable code.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Classes are the building blocks of object-oriented programming in C#. By understanding the different types of classes—abstract, static, sealed, concrete, and singleton—you can create well-structured, maintainable, and efficient code.</p>
<p>Whether you’re designing utility classes, defining abstract interfaces, or encapsulating complex logic, classes play a crucial role in shaping your application’s architecture.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Minimal API in .NET Core – A Step By Step Handbook ]]>
                </title>
                <description>
                    <![CDATA[ Minimal APIs are an exciting feature introduced in .NET 6, designed to revolutionize how you create APIs. Imagine building robust APIs with minimal code and zero boilerplate—no more wrestling with controllers, routing, or middleware. That’s what mini... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-a-minimal-api-in-net-core-handbook/</link>
                <guid isPermaLink="false">674de68794b89afe5676934e</guid>
                
                    <category>
                        <![CDATA[ dotnet ]]>
                    </category>
                
                    <category>
                        <![CDATA[ .NET ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MinimalApi ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Isaiah Clifford Opoku ]]>
                </dc:creator>
                <pubDate>Mon, 02 Dec 2024 16:55:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733158500882/9af04a12-2121-4efd-a66f-00330896e358.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Minimal APIs are an exciting feature introduced in .NET 6, designed to revolutionize how you create APIs.</p>
<p>Imagine building robust APIs with minimal code and zero boilerplate—no more wrestling with controllers, routing, or middleware. That’s what minimal APIs allow you to do. The idea with these APIs is to streamline the development process, making it incredibly easy and efficient.</p>
<p>In this article, we'll dive into the world of minimal APIs in .NET 8 and guide you through creating a fully functional bookstore API. You'll learn how to get all books, retrieve a book by its ID, add new books, and even delete books. Let’s get started.</p>
<h1 id="heading-table-of-contents">Table of Contents</h1>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-minimal-apis">Introduction to Minimal APIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-minimal-api">How to Create a Minimal API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-http-methods-in-controller-based-and-minimal-apis">HTTP Methods in Controller-based and Minimal APIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-minimal-api-project-files">Minimal API Project Files</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-models">How to Create the Models</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-database-context">How to Create the Database Context</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-contract">How to Create a Contract</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-services">How to Add Services</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-exceptions">How to Create Exceptions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-api-endpoints">How to Create the API Endpoints</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-seed-data-to-the-database">How to Add Seed Data to the Database</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-perform-a-migration">How to Perform a Migration</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-test-the-api-endpoints">How to Test the API Endpoints</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we get going, make sure you have the following prerequisites installed on your machine:</p>
<ul>
<li><p><a target="_blank" href="https://dotnet.microsoft.com/download/dotnet/8.0">.NET 8 SDK</a></p>
</li>
<li><p><a target="_blank" href="https://code.visualstudio.com/download">Visual Studio Code</a> or any other code editor of your choice</p>
</li>
<li><p><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp">C# Dev Kit</a> for Visual Studio Code</p>
</li>
</ul>
<p>Alternatively, you can use Visual Studio 2022, which comes with built-in support for .NET 8. But in this article, we'll be using Visual Studio Code. It’s lightweight, easy to use, and cross-platform.</p>
<p>We’ll use Swagger UI to test our API. Swagger UI is a powerful tool that allows you to interact with your API directly from your browser. It provides a user-friendly interface to test your API endpoints, making it easier to test and debug your API.</p>
<p>When you create a new project, it will automatically install the necessary packages and configure the project to use Swagger UI. .NET 8 includes Swagger UI by default, so whether you create your application in Visual Studio or with .NET, Swagger UI will be configured for you.</p>
<p>Run your application, and the Swagger UI will automatically open in your browser – but since we are using VS Code, we need to click on the port number on our terminal.</p>
<p>You can find the source code for this project on <a target="_blank" href="https://github.com/Clifftech123/bookapi-minimal">GitHub</a>.</p>
<h2 id="heading-introduction-to-minimal-apis">Introduction to Minimal APIs</h2>
<p>Imagine working in a codebase with numerous endpoints, making it quite large and complex. Traditionally, building an API in <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core involves using controllers, routing, middleware, and a significant amount of boilerplate code. But there are two approaches to building an API in ASP.NET Core: the traditional way and the minimal way.</p>
<p>The traditional way is familiar to most developers, involving controllers and extensive infrastructure code. The minimal way, introduced in <code>.NET 6</code>, allows you to create APIs with minimal code and zero boilerplate. This approach simplifies the development process, enabling you to focus on writing business logic rather than dealing with infrastructure code.</p>
<p>Minimal APIs are lightweight, fast, and perfect for building small to medium-sized APIs. They are ideal for prototyping, building microservices, or creating simple APIs that don't require much complexity. In this handbook, we'll explore the world of minimal APIs in .NET 6 and learn how to create a fully functional bookstore API from scratch.</p>
<h2 id="heading-how-to-create-a-minimal-api">How to Create a Minimal API</h2>
<p>Creating a minimal API is straightforward when using the <code>dotnet CLI</code>, as the default template is already a minimal API. But if you use Visual Studio, you'll need to remove the boilerplate code that comes with the project template.</p>
<p>Let's start by using the <code>dotnet CLI</code> to create a minimal API project.</p>
<pre><code class="lang-bash">
dotnet new webapi  -n BookStoreApi
</code></pre>
<p>The <code>dotnet new webapi</code> command creates a new minimal API project named <code>BookStoreApi</code>. This project contains the necessary files and folders to get you started.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732623879052/3db8614b-7b27-43ce-ad84-9fa66001b535.png" alt="Minimal API Project Files   Structure  " width="2556" height="1370" loading="lazy"></p>
<p>Let's explore the project structure:</p>
<ul>
<li><p><code>Program.cs</code>: The entry point of the application, where the host is configured.</p>
</li>
<li><p><code>bookapi-minimal.sln</code>: The solution file that contains the project.</p>
</li>
<li><p><code>bookapi-minimal.http</code>: A file that contains sample HTTP requests to test the API.</p>
</li>
<li><p><code>bookapi-minimal.csproj</code>: The project file that contains the project configuration.</p>
</li>
<li><p><code>appsettings.json</code>: The configuration file that stores application settings.</p>
</li>
<li><p><code>appsettings.Development.json</code> : The configuration file for the development environment.</p>
</li>
</ul>
<p>When you open the program.cs file, you'll notice that the code is minimal. The <code>Program.cs</code> file contains the following code:</p>
<pre><code class="lang-csharp">
<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

<span class="hljs-comment">// Add services to the container.</span>
<span class="hljs-comment">// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle</span>
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Configure the HTTP request pipeline.</span>
<span class="hljs-keyword">if</span> (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

<span class="hljs-keyword">var</span> summaries = <span class="hljs-keyword">new</span>[]
{
    <span class="hljs-string">"Freezing"</span>, <span class="hljs-string">"Bracing"</span>, <span class="hljs-string">"Chilly"</span>, <span class="hljs-string">"Cool"</span>, <span class="hljs-string">"Mild"</span>, <span class="hljs-string">"Warm"</span>, <span class="hljs-string">"Balmy"</span>, <span class="hljs-string">"Hot"</span>, <span class="hljs-string">"Sweltering"</span>, <span class="hljs-string">"Scorching"</span>
};

app.MapGet(<span class="hljs-string">"/weatherforecast"</span>, () =&gt;
{
    <span class="hljs-keyword">var</span> forecast =  Enumerable.Range(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>).Select(index =&gt;
        <span class="hljs-keyword">new</span> WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(<span class="hljs-number">-20</span>, <span class="hljs-number">55</span>),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    <span class="hljs-keyword">return</span> forecast;
})
.WithName(<span class="hljs-string">"GetWeatherForecast"</span>)
.WithOpenApi();

app.Run();

<span class="hljs-keyword">record</span> <span class="hljs-title">WeatherForecast</span>(<span class="hljs-title">DateOnly</span> <span class="hljs-title">Date</span>, <span class="hljs-title">int</span> <span class="hljs-title">TemperatureC</span>, <span class="hljs-title">string</span>? <span class="hljs-title">Summary</span>)
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> TemperatureF =&gt; <span class="hljs-number">32</span> + (<span class="hljs-keyword">int</span>)(TemperatureC / <span class="hljs-number">0.5556</span>);
}
</code></pre>
<p>If you don't fully understand the code yet, don't worry—we'll cover it in detail in the upcoming sections. The key takeaway is that minimal APIs require very little code, which is one of their main advantages.</p>
<p>The default code sets up a simple weather forecast API that you can use to test your setup. It generates a list of weather forecasts and returns them when you make a <code>GET</code> request to the <code>/weatherforecast</code> endpoint. Also, the code includes Swagger UI to help you test the API.</p>
<p>Pay special attention to the <code>app.MapGet</code> method, which maps a route to a handler function. In this case, it maps the <code>/weatherforecast</code> route to a function that returns a list of weather forecasts. We'll use similar methods to create our own endpoints in the next sections.</p>
<p>Before we start creating our project folder structure, let's understand the HTTP methods in both Controller-based and Minimal APIs.</p>
<h2 id="heading-http-methods-in-controller-based-and-minimal-apis">HTTP Methods in Controller-based and Minimal APIs</h2>
<p>In a Controller-based approach, which is the traditional way of creating web APIs, you need to create a controller class and define methods for each HTTP method. For example:</p>
<ul>
<li><p>To create a <code>GET</code> method, you use the <code>[HttpGet]</code> attribute.</p>
</li>
<li><p>To create a <code>POST</code> method, you use the <code>[HttpPost]</code> attribute.</p>
</li>
<li><p>To create a <code>PUT</code> method, you use the <code>[HttpPut]</code> attribute.</p>
</li>
<li><p>To create a <code>DELETE</code> method, you use the <code>[HttpDelete]</code> attribute.</p>
</li>
</ul>
<p>This is how endpoints are created in a Controller-based approach.</p>
<p>In contrast, Minimal APIs use methods like <code>app.MapGet</code>, <code>app.MapPost</code>, <code>app.MapPut</code>, and <code>app.MapDelete</code> to create endpoints. This is the main difference between the two approaches: Controller-based APIs use attributes to define endpoints, while Minimal APIs use methods.</p>
<p>Now that you understand how to handle HTTP requests in both Controller-based and Minimal APIs, let's create our project folder structure.</p>
<p>Before we create our project folder structure, let's first run what we have. As we learned earlier, when you create a project with either Visual Studio or .NET CLI, it comes with a default WeatherForecast project which we can run and see on the UI. Let's run it to ensure everything works before we go on to create our project folder.</p>
<p>Run this command:</p>
<pre><code class="lang-bash">
dotnet run
</code></pre>
<p>You should see the following output:</p>
<pre><code class="lang-bash">info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5228
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\Devolopemnt\Dotnet\bookapi-minimal
</code></pre>
<p>This means the application is running and listening on <a target="_blank" href="http://localhost:5228"><code>http://localhost:5228</code></a>. As I mentioned above, since we are using the <code>dotnet CLI</code> and Visual Studio Code, the application will not automatically open the browser for us. We need to do this manually.</p>
<p>Open your browser and navigate to <a target="_blank" href="http://localhost:5228/swagger/index.html"><code>http://localhost:5228/swagger/index.html</code></a> to see the default response from the API.</p>
<p>You should see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732623894640/b882a1ee-3957-4958-8f59-20b44fe7fb7d.png" alt="Swagger UI " width="2436" height="1169" loading="lazy"></p>
<p>Now the next thing for us to do is find a way to structure our project and create the necessary files and folders to get us started.</p>
<h2 id="heading-minimal-api-project-files">Minimal API Project Files</h2>
<p>To organize our project, we will create a structured folder hierarchy. This will help keep our code clean and maintainable. Here is the folder structure we will use:</p>
<ul>
<li><p><strong>AppContext</strong>: Contains the database context and related configurations.</p>
</li>
<li><p><strong>Configurations</strong>: Holds Entity Framework Core configurations and seed data for the database.</p>
</li>
<li><p><strong>Contracts</strong>: Contains Data Transfer Objects (DTOs) used in our application.</p>
</li>
<li><p><strong>Endpoints</strong>: Where we define and configure our minimal API endpoints.</p>
</li>
<li><p><strong>Exceptions</strong>: Contains custom exception classes used in the project.</p>
</li>
<li><p><strong>Extensions</strong>: Holds extension methods that we will use throughout the project.</p>
</li>
<li><p><strong>Models</strong>: Contains business logic models.</p>
</li>
<li><p><strong>Services</strong>: Contains service classes that implement business logic.</p>
</li>
<li><p><strong>Interfaces</strong>: Holds interface definitions used to map our services.</p>
</li>
</ul>
<p>In Visual Studio Code, you can create this folder structure as follows:</p>
<pre><code class="lang-bash">- AppContext
- Configurations
- Contracts
- Endpoints
- Exceptions
- Extensions
- Models
- Services
- Interfaces
</code></pre>
<p>After setting up, your project folder structure should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732623997951/8118c444-0d28-4bb7-8cad-2a9fd88c8c25.png" alt="BookApi Project Folder Structure " width="1920" height="890" loading="lazy"></p>
<p>Now that our project Structure is set up we can go ahead and start writing our code. Let's start by creating our models.</p>
<h2 id="heading-how-to-create-the-models">How to Create the Models</h2>
<p>In this section, we will create models for our application. Models are the building blocks of our application, representing the data that our application will work with. For our example, we will create a model for a book.</p>
<p>To get started, create a folder named <code>Models</code> in your project directory. Inside this folder, create a file named <code>BookModel.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Models/BookModel.cs</span>


<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Models</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookModel</span>
    {
        <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Author { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Category { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Language { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> TotalPages { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    }
}
</code></pre>
<p>This <code>BookModel class</code> defines the properties that represent the details of a book, such as its <code>title</code>, <code>author</code>, <code>description</code>, <code>category</code>, <code>language</code>, and <code>total pages</code>. Each property is designed to hold specific information about the book, making it easy to manage and manipulate book data within our application.</p>
<p>Now that we have created our model, let's create our database context.</p>
<h2 id="heading-how-to-create-the-database-context">How to Create the Database Context</h2>
<p>The database context is a class that represents a session with the database. It’s responsible for interacting with the database and executing database operations. In our application, we will use Entity Framework Core to interact with our database.</p>
<h3 id="heading-install-the-required-packages">Install the Required Packages</h3>
<p>Before creating our database context, we need to install the following packages:</p>
<ul>
<li><p><a target="_blank" href="http://Microsoft.EntityFrameworkCore.Design"><code>Microsoft.EntityFrameworkCore.Design</code></a></p>
</li>
<li><p><code>Microsoft.EntityFrameworkCore</code></p>
</li>
<li><p><code>Microsoft.EntityFrameworkCore.SqlServer</code></p>
</li>
<li><p><a target="_blank" href="http://Microsoft.EntityFrameworkCore.Tools"><code>Microsoft.EntityFrameworkCore.Tools</code></a></p>
</li>
<li><p><code>FluentValidation.DependencyInjectionExtensions</code></p>
</li>
</ul>
<p>You can install these packages using the following commands:</p>
<pre><code class="lang-bash">dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package FluentValidation.DependencyInjectionExtensions
</code></pre>
<h3 id="heading-verify-package-installation">Verify Package Installation</h3>
<p>To verify that the packages are installed, open the <code>bookapi-minimal.csproj</code> file in your project's root directory. You should see the installed packages listed as follows:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk.Web"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net8.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Nullable</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">Nullable</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ImplicitUsings</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">ImplicitUsings</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RootNamespace</span>&gt;</span>bookapi_minimal<span class="hljs-tag">&lt;/<span class="hljs-name">RootNamespace</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"FluentValidation.DependencyInjectionExtensions"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"11.9.2"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.AspNetCore.OpenApi"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.6"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.Design"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">IncludeAssets</span>&gt;</span>runtime; build; native; contentfiles; analyzers; buildtransitive<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeAssets</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PrivateAssets</span>&gt;</span>all<span class="hljs-tag">&lt;/<span class="hljs-name">PrivateAssets</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">PackageReference</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.SqlServer"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.Tools"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">IncludeAssets</span>&gt;</span>runtime; build; native; contentfiles; analyzers; buildtransitive<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeAssets</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PrivateAssets</span>&gt;</span>all<span class="hljs-tag">&lt;/<span class="hljs-name">PrivateAssets</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">PackageReference</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Swashbuckle.AspNetCore"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"6.4.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>This confirms that the packages have been successfully installed.</p>
<p>Now let's create our database context.</p>
<p>In the AppContext folder, create a new file named <code>ApplicationContext.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// AppContext/ApplicationContext.cs</span>

<span class="hljs-keyword">using</span> bookapi_minimal.Models;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.AppContext</span>
{

    <span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">ApplicationContext</span>(<span class="hljs-params">DbContextOptions&lt;ApplicationContext&gt; options</span>) : <span class="hljs-title">DbContext</span>(<span class="hljs-params">options</span>)</span>
    {

        <span class="hljs-comment">// Default schema for the database context</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> DefaultSchema = <span class="hljs-string">"bookapi"</span>;


       <span class="hljs-comment">// DbSet to represent the collection of books in our database</span>
        <span class="hljs-keyword">public</span> DbSet&lt;BookModel&gt; Books { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-comment">// Constructor to configure the database context</span>

        <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnModelCreating</span>(<span class="hljs-params">ModelBuilder modelBuilder</span>)</span>
        {
            <span class="hljs-keyword">base</span>.OnModelCreating(modelBuilder);
            modelBuilder.HasDefaultSchema(DefaultSchema);

            modelBuilder.ApplyConfigurationsFromAssembly(<span class="hljs-keyword">typeof</span>(ApplicationContext).Assembly);

            modelBuilder.ApplyConfigurationsFromAssembly(<span class="hljs-keyword">typeof</span>(ApplicationContext).Assembly);

        }

    }
}
</code></pre>
<p>Let's break down the code above:</p>
<ul>
<li><p>We define a class named <code>ApplicationContext</code> that inherits from <code>DbContext</code>. The <code>DbContext</code> class is part of Entity Framework Core and represents a session with the database.</p>
</li>
<li><p>The constructor accepts an instance of <code>DbContextOptions&lt;ApplicationContext&gt;</code>. This constructor is used to configure the database context options.</p>
</li>
<li><p>We define a property named <code>Books</code> type <code>DbSet&lt;BookModel&gt;</code>. This property represents the collection of books in our database.</p>
</li>
<li><p>We override the <code>OnModelCreating</code> method to configure the database schema and apply any configurations defined in our application.</p>
</li>
</ul>
<p>Now that we have created our database context, let's create our extension method and register our database context in the dependency injection container.</p>
<h3 id="heading-create-an-extension-method">Create an Extension Method</h3>
<p>Before we create the extension method, let's understand what an extension method is in the context of <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core.</p>
<p>An extension method is a static method that adds new functionality to an existing type without modifying the original type. In <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core, extension methods are commonly used to extend the functionality of the <code>IServiceCollection</code> interface, which is used to register services in the dependency injection container.</p>
<p>Services are components that provide functionality to an application, such as database access, logging, and configuration. By creating an extension method for the <code>IServiceCollection</code> interface, you can simplify the process of registering your services in the dependency injection container.</p>
<p>Instead of putting everything in the <code>Program.cs</code> file, we will create an extension method to register our services in the dependency injection container. This will help us keep our code clean and organized.</p>
<p>In the <code>Extensions</code> folder, create a new file named <code>ServiceExtensions.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System.Reflection;
<span class="hljs-keyword">using</span> bookapi_minimal.AppContext;
<span class="hljs-keyword">using</span> FluentValidation;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Extensions</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ServiceExtensions</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddApplicationServices</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> IHostApplicationBuilder builder</span>)</span>
        {
            <span class="hljs-keyword">if</span> (builder == <span class="hljs-literal">null</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentNullException(<span class="hljs-keyword">nameof</span>(builder));
            <span class="hljs-keyword">if</span> (builder.Configuration == <span class="hljs-literal">null</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentNullException(<span class="hljs-keyword">nameof</span>(builder.Configuration));

            <span class="hljs-comment">// Adding the database context</span>
            builder.Services.AddDbContext&lt;ApplicationContext&gt;(configure =&gt;
            {
                configure.UseSqlServer(builder.Configuration.GetConnectionString(<span class="hljs-string">"sqlConnection"</span>));
            });

            <span class="hljs-comment">// Adding validators from the current assembly</span>
            builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
        }
    }
}
</code></pre>
<p>Let's break down the code above:</p>
<ul>
<li><p>We define a static class named <code>ServiceExtensions</code> that contains an extension method named <code>AddApplicationServices</code>. This method extends the <code>IHostApplicationBuilder</code> interface, which is used to configure the application's request processing pipeline.</p>
</li>
<li><p>The <code>AddApplicationServices</code> method accepts an instance of <code>IHostApplicationBuilder</code> as a parameter. This parameter is used to access the application's configuration and services.</p>
</li>
<li><p>We add the <code>ApplicationContext</code> to the dependency injection container and configure it to use SQL Server as the database provider. We retrieve the connection string from the <code>appsettings.json</code> file using the <code>GetConnectionString</code> method.</p>
</li>
<li><p>We add <code>validators</code> from the current <code>assembly</code> using the <code>AddValidatorsFromAssembly</code> method. This method scans the current assembly for classes that implement the IValidator interface and registers them in the dependency injection container.</p>
</li>
</ul>
<p>Next, we need to add the connection string to the <code>appsettings.json</code> file. Add the following code to your <code>appsettings.json</code> file:</p>
<pre><code class="lang-json">{ 
     <span class="hljs-attr">"ConnectionStrings"</span>: {
    <span class="hljs-attr">"sqlConnection"</span>: <span class="hljs-string">"Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"</span>
  }
  }
</code></pre>
<p>Make sure to replace <code>your_password</code> it with your actual SQL Server password.</p>
<p>Your <code>appsettings.json</code> file should look like this:</p>
<pre><code class="lang-json">
{
  <span class="hljs-attr">"Logging"</span>: {
    <span class="hljs-attr">"LogLevel"</span>: {
      <span class="hljs-attr">"Default"</span>: <span class="hljs-string">"Information"</span>,
      <span class="hljs-attr">"Microsoft.AspNetCore"</span>: <span class="hljs-string">"Warning"</span>
    }
  },
  <span class="hljs-attr">"ConnectionStrings"</span>: {
    <span class="hljs-attr">"sqlConnection"</span>: <span class="hljs-string">"Server=localhost\\SQLEXPRESS02;Database=BookAPIMinimalAPI;Integrated Security=true;TrustServerCertificate=true;"</span>
  },
  <span class="hljs-attr">"AllowedHosts"</span>: <span class="hljs-string">"*"</span>
}
</code></pre>
<p>Congratulations! You have successfully created the database context, extension method, and connection string for your application. In the next section, we will create a Contract.</p>
<h2 id="heading-how-to-create-a-contract">How to Create a Contract</h2>
<p>Contracts are Data Transfer Objects (DTOs) that define the structure of the data exchanged between the client and the server. In our application, we will create contracts to represent the data sent and received by our API endpoints.</p>
<p>Here are the contracts we are going to create:</p>
<ul>
<li><p>CreateBookRequest: This represents the data sent when creating a new book.</p>
</li>
<li><p>UpdateBookRequest: tHI Represents the data sent when updating an existing book.</p>
</li>
<li><p>BookResponse: Represents the data returned when retrieving a book.</p>
</li>
<li><p>ErrorResponse: Represents the error response returned when an exception occurs.</p>
</li>
<li><p>ApiResponse: Represents the response returned by the API.</p>
</li>
</ul>
<p>In the <code>Contracts</code> folder, create a new file named <code>CreateBookRequest</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Contracts/CreateBookRequest.cs</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Contracts</span>
{

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span> <span class="hljs-title">CreateBookRequest</span>
    { 

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Author { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Category { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Language { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> TotalPages { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
    }
}
</code></pre>
<p>In the <code>Contracts</code> folder, create a new file named <code>UpdateBookRequest</code> and add the following code:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Contracts/UpdateBookRequest.cs</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Contracts</span>
{

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span> <span class="hljs-title">UpdateBookRequest</span>
    {
       <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Author { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Category { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Language { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> TotalPages { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    }
}
</code></pre>
<p>In the <code>Contracts</code> folder, create a new file named <code>BookResponse</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Contracts/BookResponse.cs</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Contracts</span>
{

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span> <span class="hljs-title">BookResponse</span>
    {
        <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Author { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Category { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Language { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> TotalPages { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    }
}
</code></pre>
<p>In the <code>Contracts</code> folder, create a new file named <code>ErrorResponse</code> and add the following code:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Contracts/ErrorResponse.cs</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Contracts</span>
{

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span> <span class="hljs-title">ErrorResponse</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> StatusCode { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Message { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    }

}
</code></pre>
<p>In the <code>Contracts</code> folder, create a new file named <code>ApiResponse</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Contracts/ApiResponse.cs</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Contracts</span>
{

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApiResponse</span>&lt;<span class="hljs-title">T</span>&gt;
    {
        <span class="hljs-keyword">public</span> T Data { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Message { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ApiResponse</span>(<span class="hljs-params">T data, <span class="hljs-keyword">string</span> message</span>)</span>
        {
            Data = data;
            Message = message;
        }
    }
}
</code></pre>
<p>These contracts help us define the structure of the data exchanged between the client and the server, making it easier to work with the data in our application.</p>
<p>In the next section, we will create services to implement the business logic of our application.</p>
<h2 id="heading-how-to-add-services">How to Add Services</h2>
<p>Services are components that provide functionality to an application. In our application, we will create services to implement the business logic of our application. We will create services to handle CRUD operations for books, validate book data, and handle exceptions.</p>
<p>In ASP.NET Core, services are registered in the dependency injection container and can be injected into other components, such as controllers and endpoints, But this is a minimal API so we will inject the services directly into the endpoints.</p>
<p>Let's create an interface for our services. In the <code>Interfaces</code> folder, create a new file named <code>IBookService.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"> <span class="hljs-comment">// Interfaces/IBookService.cs</span>



<span class="hljs-keyword">using</span> bookapi_minimal.Contracts;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Interfaces</span>
{
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IBookService</span>
    {
        <span class="hljs-function">Task&lt;BookResponse&gt; <span class="hljs-title">AddBookAsync</span>(<span class="hljs-params">CreateBookRequest createBookRequest</span>)</span>;
        <span class="hljs-function">Task&lt;BookResponse&gt; <span class="hljs-title">GetBookByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>;
        Task&lt;IEnumerable&lt;BookResponse&gt;&gt; GetBooksAsync();
        <span class="hljs-function">Task&lt;BookResponse&gt; <span class="hljs-title">UpdateBookAsync</span>(<span class="hljs-params">Guid id,  UpdateBookRequest  updateBookRequest</span>)</span>;
        <span class="hljs-function">Task&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">DeleteBookAsync</span>(<span class="hljs-params">Guid id</span>)</span>;
    }
}
</code></pre>
<p>Let's break down the code above: We have defined an interface named <code>IBookService</code> that contains methods to handle CRUD operations for books. The interface defines the following methods:</p>
<ul>
<li><p><code>AddBookAsync</code>: Adds a new book to the database.</p>
</li>
<li><p><code>GetBookByIdAsync</code>: Retrieves a book by its ID.</p>
</li>
<li><p><code>GetBooksAsync</code>: Retrieves all books from the database.</p>
</li>
<li><p><code>UpdateBookAsync</code>: Updates an existing book.</p>
</li>
</ul>
<p>We are using the Contract we created earlier in the <code>Contracts</code> folder. The <code>IBookService</code> interface defines the structure of the methods that will be implemented by the service classes. This helps us separate the interface from the implementation, making it easier to maintain and test our code.</p>
<p>Now that we have created the interface, let's create the service class that implements the interface.</p>
<h3 id="heading-how-to-implement-the-book-service">How to Implement the Book Service</h3>
<p>This service will implement the <code>IBookService</code> interface and provide the business logic for our application. In the <code>Services</code> folder, create a new file named <code>BookService.cs</code> . Your initial file should look like this:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Services/BookService.cs</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookService</span>
    {

    }
}
</code></pre>
<p>The first thing we need to do is add the interface to the <code>BookService</code> class. Update the <code>BookService</code> class to implement the <code>IBookService</code> interface as follows:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Services/BookService.cs</span>



<span class="hljs-keyword">using</span> bookapi_minimal.Interfaces;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookService</span>:<span class="hljs-title">IBookService</span>
    {

    }
}
</code></pre>
<p>When you do this, your VS Code might show an error because we have not implemented the methods in the interface. Let's go ahead and implement the methods in the <code>BookService</code> class.</p>
<p>In VS Code you can use the <code>Ctrl + .</code> shortcut to implement the methods in the interface. Then you will see the following code generated for you:</p>
<pre><code class="lang-csharp">
<span class="hljs-keyword">using</span> bookapi_minimal.Contracts;
<span class="hljs-keyword">using</span> bookapi_minimal.Interfaces;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Services</span>
{
     <span class="hljs-comment">// Service class for managing books</span>
   <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookService</span> : <span class="hljs-title">IBookService</span>
   {
       <span class="hljs-comment">// Method to add a new book to the database</span>
       <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;BookResponse&gt; <span class="hljs-title">AddBookAsync</span>(<span class="hljs-params">CreateBookRequest createBookRequest</span>)</span>
       {
           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
       }

      <span class="hljs-comment">// Method to Delete a book from the database</span>
       <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">DeleteBookAsync</span>(<span class="hljs-params">Guid id</span>)</span>
       {
           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
       }

       <span class="hljs-comment">// Method to Get a book from the database by its ID</span>

       <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;BookResponse&gt; <span class="hljs-title">GetBookByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
       {
           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
       }

      <span class="hljs-comment">// Method to Get all books from the database</span>
       <span class="hljs-keyword">public</span> Task&lt;IEnumerable&lt;BookResponse&gt;&gt; GetBooksAsync()
       {
           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
       }

       <span class="hljs-comment">// Method to Update a book in the database</span>
       <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;BookResponse&gt; <span class="hljs-title">UpdateBookAsync</span>(<span class="hljs-params">Guid id, UpdateBookRequest updateBookRequest</span>)</span>
       {
           <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
       }
   }
}
</code></pre>
<p>Now you can see that the methods in the interface have been implemented in the <code>BookService</code> class. We will implement the business logic for each method in the next section.</p>
<p>Before we do that, let's add the necessary dependencies to the <code>BookService</code> class. We need to inject the <code>ApplicationContext</code> and <code>ILogger</code> dependencies into the <code>BookService</code> class. <code>ApplicationContext</code> is used to interact with the database, while <code>ILogger</code> is used for logging.</p>
<p>To inject the dependencies, update the <code>BookService</code> class as follows:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Services/BookService.cs</span>

<span class="hljs-comment">// ...</span>
 <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ApplicationContext _context; <span class="hljs-comment">// Database context</span>
  <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;BookService&gt; _logger; <span class="hljs-comment">// Logger for logging information and errors</span>

<span class="hljs-comment">//..</span>
</code></pre>
<p>Since we have added the dependencies, we need to update the <code>BookService</code> constructor to accept the dependencies. Update the <code>BookService</code> constructor as follows:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Services/BookService.cs</span>

<span class="hljs-comment">// ...</span>

  <span class="hljs-comment">// Constructor to initialize the database context and logger</span>
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BookService</span>(<span class="hljs-params">ApplicationContext context, ILogger&lt;BookService&gt; logger</span>)</span>
 {
            _context = context;
            _logger = logger;
}

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Now that we have added the dependencies and updated the constructor, we can implement the business logic for each method in the <code>BookService</code> class.</p>
<p>Let's create logic for the CREATE, READ, UPDATE, and DELETE operations in the <code>BookService</code> class.</p>
<h3 id="heading-how-to-implement-the-addbookasync-method">How to Implement the <code>AddBookAsync</code> Method</h3>
<p>As I mentioned earlier, we’ll use the <code>AddBookAsync</code> method to add a new book to the database. In this method, we will create a new book entity, map the data from the <code>CreateBookRequest</code> object to the book entity, and save the book entity to the database. We will also return the book entity as an <code>BookResponse</code> object.</p>
<p>Update the <code>AddBookAsync</code> method in the <code>BookService</code> class as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/BookService.cs</span>

<span class="hljs-comment">// ...</span>
 <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Add a new book</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="createBookRequest"&gt;</span>Book request to be added<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>Details of the created book<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;BookResponse&gt; <span class="hljs-title">AddBookAsync</span>(<span class="hljs-params">CreateBookRequest createBookRequest</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">var</span> book = <span class="hljs-keyword">new</span> BookModel
                {
                    Title = createBookRequest.Title,
                    Author = createBookRequest.Author,
                    Description = createBookRequest.Description,
                    Category = createBookRequest.Category,
                    Language = createBookRequest.Language,
                    TotalPages = createBookRequest.TotalPages
                };

                <span class="hljs-comment">// Add the book to the database</span>
                _context.Books.Add(book);
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
                _logger.LogInformation(<span class="hljs-string">"Book added successfully."</span>);

                <span class="hljs-comment">// Return the details of the created book</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = book.Id,
                    Title = book.Title,
                    Author = book.Author,
                    Description = book.Description,
                    Category = book.Category,
                    Language = book.Language,
                    TotalPages = book.TotalPages
                };
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error adding book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }
<span class="hljs-comment">// ...</span>
</code></pre>
<p>In this code, we are creating a new book entity from the <code>CreateBookRequest</code> object, mapping the data from the <code>CreateBookRequest</code> object to the book entity, saving the book entity to the database, and returning the book entity as a <code>BookResponse</code> object.</p>
<p>We are also logging information and errors using the <code>ILogger</code> dependency. If an exception occurs during the process, we log the error message and rethrow the exception.</p>
<p>Now that we have implemented the <code>AddBookAsync</code> method, let's implement the <code>GetBookByIdAsync</code> method.</p>
<h3 id="heading-how-to-implement-the-getbookbyidasync-method">How to Implement the <code>GetBookByIdAsync</code> Method</h3>
<p>The <code>GetBookByIdAsync</code> method is used to retrieve a book by its ID from the database. In this method, we will query the database for the book with the specified ID, map the book entity to a <code>BookResponse</code> object, and return the <code>BookResponse</code> object.</p>
<p>Update the <code>GetBookByIdAsync</code> method in the <code>BookService</code> class as follows:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Services/BookService.cs</span>

<span class="hljs-comment">//... </span>

    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Get a book by its ID</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="id"&gt;</span>ID of the book<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>Details of the book<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;BookResponse&gt;  <span class="hljs-title">GetBookByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Find the book by its ID</span>
                <span class="hljs-keyword">var</span> book = <span class="hljs-keyword">await</span> _context.Books.FindAsync(id);
                <span class="hljs-keyword">if</span> (book == <span class="hljs-literal">null</span>)
                {
                    _logger.LogWarning(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> not found."</span>);
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
                }

                <span class="hljs-comment">// Return the details of the book</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = book.Id,
                    Title = book.Title,
                    Author = book.Author,
                    Description = book.Description,
                    Category = book.Category,
                    Language = book.Language,
                    TotalPages = book.TotalPages
                };
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error retrieving book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }

<span class="hljs-comment">//...</span>
</code></pre>
<p>In this code, we are querying the database for the book with the specified ID, mapping the book entity to a <code>BookResponse</code> object, and returning the <code>BookResponse</code> object. We are also logging information and errors using the <code>ILogger</code> dependency.</p>
<p>If the book with the specified ID is not found, we log a warning message and return null. If an exception occurs during the process, we log the error message and rethrow the exception.</p>
<p>Now that we have implemented the <code>GetBookByIdAsync</code> method, let's implement the <code>GetBooksAsync</code> method.</p>
<h3 id="heading-how-to-implement-the-getbooksasync-method">How to Implement the <code>GetBooksAsync</code> Method</h3>
<p>The <code>GetBooksAsync</code> method is used to retrieve all books from the database. In this method, we will query the database for all books, map each book entity to a <code>BookResponse</code> object, and return a list of <code>BookResponse</code> objects.</p>
<p>Update the <code>GetBooksAsync</code> method in the <code>BookService</code> class as follows:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Services/BookService.cs</span>

<span class="hljs-comment">//... </span>


  <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Get all books</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>List of all books<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IEnumerable&lt;BookResponse&gt;&gt; GetBooksAsync()
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Get all books from the database</span>
                <span class="hljs-keyword">var</span> books = <span class="hljs-keyword">await</span> _context.Books.ToListAsync();

                <span class="hljs-comment">// Return the details of all books</span>
                <span class="hljs-keyword">return</span> books.Select(book =&gt; <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = book.Id,
                    Title = book.Title,
                    Author = book.Author,
                    Description = book.Description,
                    Category = book.Category,
                    Language = book.Language,
                    TotalPages = book.TotalPages
                });
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error retrieving books: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }
<span class="hljs-comment">//...</span>
</code></pre>
<p>Here, we are querying the database for all books, mapping each book entity to an <code>BookResponse</code> object, and returning a list of <code>BookResponse</code> objects. We are also logging information and errors using the <code>ILogger</code> dependency. If an exception occurs during the process, we log the error message and rethrow the exception.</p>
<p>Now that we have implemented the <code>GetBooksAsync</code> method, let's implement the <code>UpdateBookAsync</code> method.</p>
<h3 id="heading-how-to-implement-the-updatebookasync-method">How to Implement the <code>UpdateBookAsync</code> Method</h3>
<p>The <code>UpdateBookAsync</code> method is used to update an existing book in the database. In this method, we will query the database for the book with the specified ID, update the book entity with the data from the <code>UpdateBookRequest</code> object, save the updated book entity to the database, and return the updated book entity as a <code>BookResponse</code> object.</p>
<p>Update the <code>UpdateBookAsync</code> method in the <code>BookService</code> class as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/BookService.cs</span>
 <span class="hljs-comment">//...</span>
 <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Update an existing book</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="id"&gt;</span>ID of the book to be updated<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="book"&gt;</span>Updated book model<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>Details of the updated book<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;BookResponse&gt; <span class="hljs-title">UpdateBookAsync</span>(<span class="hljs-params">Guid id, UpdateBookRequest book</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Find the existing book by its ID</span>
                <span class="hljs-keyword">var</span> existingBook = <span class="hljs-keyword">await</span> _context.Books.FindAsync(id);
                <span class="hljs-keyword">if</span> (existingBook == <span class="hljs-literal">null</span>)
                {
                    _logger.LogWarning(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> not found."</span>);
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
                }

                <span class="hljs-comment">// Update the book details</span>
                existingBook.Title = book.Title;
                existingBook.Author = book.Author;
                existingBook.Description = book.Description;
                existingBook.Category = book.Category;
                existingBook.Language = book.Language;
                existingBook.TotalPages = book.TotalPages;

                <span class="hljs-comment">// Save the changes to the database</span>
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
                _logger.LogInformation(<span class="hljs-string">"Book updated successfully."</span>);

                <span class="hljs-comment">// Return the details of the updated book</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = existingBook.Id,
                    Title = existingBook.Title,
                    Author = existingBook.Author,
                    Description = existingBook.Description,
                    Category = existingBook.Category,
                    Language = existingBook.Language,
                    TotalPages = existingBook.TotalPages
                };
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error updating book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }
<span class="hljs-comment">//...</span>
</code></pre>
<p>Here, we are querying the database for the book with the specified ID, updating the book entity with the data from the <code>UpdateBookRequest</code> object, saving the updated book entity to the database, and returning the updated book entity as a <code>BookResponse</code> object. We are also logging information and errors using the <code>ILogger</code> dependency.</p>
<p>If the book with the specified ID is not found, we log a warning message and return null. If an exception occurs during the process, we log the error message and rethrow the exception.</p>
<p>Now that we have implemented the <code>UpdateBookAsync</code> method, let's implement the <code>DeleteBookAsync</code> method.</p>
<h3 id="heading-how-to-implement-the-deletebookasync-method">How to Implement the <code>DeleteBookAsync</code> Method</h3>
<p>The <code>DeleteBookAsync</code> method is used to delete an existing book from the database. In this method, we will query the database for the book with the specified ID, remove the book entity from the database, and return a boolean value indicating whether the book was successfully deleted.</p>
<p>Update the <code>DeleteBookAsync</code> method in the <code>BookService</code> class as follows:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/BookService.cs</span>

 <span class="hljs-comment">//...</span>


<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Delete a book by its ID</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="id"&gt;</span>ID of the book to be deleted<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>True if the book was deleted, false otherwise<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">DeleteBookAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Find the book by its ID</span>
                <span class="hljs-keyword">var</span> book = <span class="hljs-keyword">await</span> _context.Books.FindAsync(id);
                <span class="hljs-keyword">if</span> (book == <span class="hljs-literal">null</span>)
                {
                    _logger.LogWarning(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> not found."</span>);
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
                }

                <span class="hljs-comment">// Remove the book from the database</span>
                _context.Books.Remove(book);
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
                _logger.LogInformation(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> deleted successfully."</span>);
                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error deleting book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }
<span class="hljs-comment">//...</span>
</code></pre>
<p>In this code, we are querying the database for the book with the specified ID, removing the book entity from the database, and returning a boolean value indicating whether the book was successfully deleted. We are also logging information and errors using the <code>ILogger</code> dependency.</p>
<p>If the book with the specified ID is not found, we log a warning message and return false. If an exception occurs during the process, we log the error message and rethrow the exception.</p>
<p>Now you have successfully implemented the business logic for the <code>AddBookAsync</code>, <code>GetBookByIdAsync</code>, <code>GetBooksAsync</code>, <code>UpdateBookAsync</code>, and <code>DeleteBookAsync</code> methods in the <code>BookService</code> class. These methods handle the CRUD operations for books, validate book data, and handle exceptions. By now, your <code>BookService</code> class should look like this:</p>
<pre><code class="lang-csharp">

<span class="hljs-keyword">using</span> bookapi_minimal.AppContext;
<span class="hljs-keyword">using</span> bookapi_minimal.Contracts;
<span class="hljs-keyword">using</span> bookapi_minimal.Interfaces;
<span class="hljs-keyword">using</span> bookapi_minimal.Models;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookService</span> : <span class="hljs-title">IBookService</span>
    {
          <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ApplicationContext _context; <span class="hljs-comment">// Database context</span>
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;BookService&gt; _logger; <span class="hljs-comment">// Logger for logging information and error</span>
          <span class="hljs-comment">// Constructor to initialize the database context and logger</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BookService</span>(<span class="hljs-params">ApplicationContext context, ILogger&lt;BookService&gt; logger</span>)</span>
        {
            _context = context;
            _logger = logger;
        }

           <span class="hljs-comment"><span class="hljs-doctag">///</span> Add a new book</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="createBookRequest"&gt;</span>Book request to be added<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>Details of the created book<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;BookResponse&gt; <span class="hljs-title">AddBookAsync</span>(<span class="hljs-params">CreateBookRequest createBookRequest</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">var</span> book = <span class="hljs-keyword">new</span> BookModel
                {
                    Title = createBookRequest.Title,
                    Author = createBookRequest.Author,
                    Description = createBookRequest.Description,
                    Category = createBookRequest.Category,
                    Language = createBookRequest.Language,
                    TotalPages = createBookRequest.TotalPages
                };

                <span class="hljs-comment">// Add the book to the database</span>
                _context.Books.Add(book);
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
                _logger.LogInformation(<span class="hljs-string">"Book added successfully."</span>);

                <span class="hljs-comment">// Return the details of the created book</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = book.Id,
                    Title = book.Title,
                    Author = book.Author,
                    Description = book.Description,
                    Category = book.Category,
                    Language = book.Language,
                    TotalPages = book.TotalPages
                };
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error adding book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }

          <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Get a book by its ID</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="id"&gt;</span>ID of the book<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>Details of the book<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;BookResponse&gt;  <span class="hljs-title">GetBookByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Find the book by its ID</span>
                <span class="hljs-keyword">var</span> book = <span class="hljs-keyword">await</span> _context.Books.FindAsync(id);
                <span class="hljs-keyword">if</span> (book == <span class="hljs-literal">null</span>)
                {
                    _logger.LogWarning(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> not found."</span>);
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
                }

                <span class="hljs-comment">// Return the details of the book</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = book.Id,
                    Title = book.Title,
                    Author = book.Author,
                    Description = book.Description,
                    Category = book.Category,
                    Language = book.Language,
                    TotalPages = book.TotalPages
                };
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error retrieving book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }



  <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Get all books</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>List of all books<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IEnumerable&lt;BookResponse&gt;&gt; GetBooksAsync()
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Get all books from the database</span>
                <span class="hljs-keyword">var</span> books = <span class="hljs-keyword">await</span> _context.Books.ToListAsync();

                <span class="hljs-comment">// Return the details of all books</span>
                <span class="hljs-keyword">return</span> books.Select(book =&gt; <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = book.Id,
                    Title = book.Title,
                    Author = book.Author,
                    Description = book.Description,
                    Category = book.Category,
                    Language = book.Language,
                    TotalPages = book.TotalPages
                });
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error retrieving books: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }


         <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Update an existing book</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="id"&gt;</span>ID of the book to be updated<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="book"&gt;</span>Updated book model<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>Details of the updated book<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;BookResponse&gt; <span class="hljs-title">UpdateBookAsync</span>(<span class="hljs-params">Guid id, UpdateBookRequest book</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Find the existing book by its ID</span>
                <span class="hljs-keyword">var</span> existingBook = <span class="hljs-keyword">await</span> _context.Books.FindAsync(id);
                <span class="hljs-keyword">if</span> (existingBook == <span class="hljs-literal">null</span>)
                {
                    _logger.LogWarning(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> not found."</span>);
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
                }

                <span class="hljs-comment">// Update the book details</span>
                existingBook.Title = book.Title;
                existingBook.Author = book.Author;
                existingBook.Description = book.Description;
                existingBook.Category = book.Category;
                existingBook.Language = book.Language;
                existingBook.TotalPages = book.TotalPages;

                <span class="hljs-comment">// Save the changes to the database</span>
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
                _logger.LogInformation(<span class="hljs-string">"Book updated successfully."</span>);

                <span class="hljs-comment">// Return the details of the updated book</span>
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BookResponse
                {
                    Id = existingBook.Id,
                    Title = existingBook.Title,
                    Author = existingBook.Author,
                    Description = existingBook.Description,
                    Category = existingBook.Category,
                    Language = existingBook.Language,
                    TotalPages = existingBook.TotalPages
                };
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error updating book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }



        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Delete a book by its ID</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="id"&gt;</span>ID of the book to be deleted<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>True if the book was deleted, false otherwise<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">DeleteBookAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-comment">// Find the book by its ID</span>
                <span class="hljs-keyword">var</span> book = <span class="hljs-keyword">await</span> _context.Books.FindAsync(id);
                <span class="hljs-keyword">if</span> (book == <span class="hljs-literal">null</span>)
                {
                    _logger.LogWarning(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> not found."</span>);
                    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
                }

                <span class="hljs-comment">// Remove the book from the database</span>
                _context.Books.Remove(book);
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
                _logger.LogInformation(<span class="hljs-string">$"Book with ID <span class="hljs-subst">{id}</span> deleted successfully."</span>);
                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(<span class="hljs-string">$"Error deleting book: <span class="hljs-subst">{ex.Message}</span>"</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }

    }
}
</code></pre>
<p>Congratulations! You have successfully implemented the business logic for the <code>AddBookAsync</code>, <code>GetBookByIdAsync</code>, <code>GetBooksAsync</code>, <code>UpdateBookAsync</code>, and <code>DeleteBookAsync</code> methods in the <code>BookService</code> class.</p>
<p>There's one thing we need to do: we need to register the service in our extension method. Let's go ahead and do that.</p>
<p>In your <code>ServiceExtensions.cs</code> file, add the following code:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Extensions/ServiceExtensions.cs</span>

<span class="hljs-comment">//..</span>

 builder.Services.AddScoped&lt;IBookService, BookService&gt;();
<span class="hljs-comment">//...</span>
</code></pre>
<p>This will register the <code>BookService</code> class as a scoped service. This means that the service will be created once per request and disposed of after the request is complete.</p>
<p>Now that we have the service working, let's go ahead and create the exception classes.</p>
<h2 id="heading-how-to-create-exceptions">How to Create Exceptions</h2>
<p>Properly handling exceptions is crucial for ensuring the stability and reliability of an application. In the context of ASP.NET Core, there are two main types of exceptions:</p>
<ul>
<li><p><strong>System Exceptions</strong>: These are exceptions thrown by the .NET runtime or the underlying system.</p>
</li>
<li><p><strong>Application Exceptions</strong>: These are exceptions thrown by the application code to handle specific errors or conditions.</p>
</li>
</ul>
<p>In ASP.NET Core with .NET 8, a new feature called global exception handling was introduced. This feature allows you to handle exceptions globally in your application, making it easier to manage errors and provide a consistent user experience.</p>
<p>In our application, we will create custom exception classes to handle specific errors and conditions. We’ll also leverage the global exception handling feature to manage exceptions globally, ensuring a uniform approach to error handling across the entire application.</p>
<p>We are going to create the following exception classes:</p>
<ul>
<li><p><code>NoBookFoundException</code>: Thrown when a book with the specified ID is not found.</p>
</li>
<li><p><code>BookDoesNotExistException</code>: Thrown when a book with the specified ID does not exist.</p>
</li>
<li><p><code>GlobalExceptionHandler</code>: Handles exceptions globally in the application.</p>
</li>
</ul>
<p>In the <code>Exceptions</code> folder, create a new file named <code>NoBookFoundException.cs</code> and add the following code:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Exceptions/NoBookFoundException.cs</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Exceptions</span>
{

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">NoBookFoundException</span> : <span class="hljs-title">Exception</span>
    {

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">NoBookFoundException</span>(<span class="hljs-params"></span>) : <span class="hljs-title">base</span>(<span class="hljs-params"><span class="hljs-string">"No books found"</span></span>)</span>
        {}
    }
}
</code></pre>
<p>In this code, we are creating a custom exception class named <code>NoBookFoundException</code> that inherits from the <code>Exception</code> class. The <code>NoBookFoundException</code> class is used to handle the scenario where no books are found in the database. We are also providing a custom error message for the exception.</p>
<p>In the <code>Exceptions</code> folder, create a new file named <code>BookDoesNotExistException.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Exceptions</span>
{
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookDoesNotExistException</span> : <span class="hljs-title">Exception</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BookDoesNotExistException</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>) : <span class="hljs-title">base</span>(<span class="hljs-params"><span class="hljs-string">$"Book with id <span class="hljs-subst">{id}</span> does not exist"</span></span>)</span>
        {
            <span class="hljs-keyword">this</span>.id = id;
        } 

    }
}
</code></pre>
<p>In this code, we are creating a custom exception class named <code>BookDoesNotExistException</code> that inherits from the <code>Exception</code> class. The <code>BookDoesNotExistException</code> class is used to handle the scenario where a book with the specified ID does not exist in the database. We are also providing a custom error message for the exception.</p>
<p>In the <code>Exceptions</code> folder, create a new file named <code>GlobalExceptionHandler.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Exceptions/GlobalExceptionHandler.cs</span>

<span class="hljs-keyword">using</span> System.Net;
<span class="hljs-keyword">using</span> bookapi_minimal.Contracts;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Diagnostics;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Exceptions</span>
{

   <span class="hljs-comment">// Global exception handler class implementing IExceptionHandler</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> : <span class="hljs-title">IExceptionHandler</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;GlobalExceptionHandler&gt; _logger;

        <span class="hljs-comment">// Constructor to initialize the logger</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">GlobalExceptionHandler</span>(<span class="hljs-params">ILogger&lt;GlobalExceptionHandler&gt; logger</span>)</span>
        {
            _logger = logger;
        }

        <span class="hljs-comment">// Method to handle exceptions asynchronously</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> ValueTask&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">TryHandleAsync</span>(<span class="hljs-params">
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken</span>)</span>
        {
            <span class="hljs-comment">// Log the exception details</span>
            _logger.LogError(exception, <span class="hljs-string">"An error occurred while processing your request"</span>);

            <span class="hljs-keyword">var</span> errorResponse = <span class="hljs-keyword">new</span> ErrorResponse
            {
                Message = exception.Message,
                Title = exception.GetType().Name
            };

            <span class="hljs-comment">// Determine the status code based on the type of exception</span>
            <span class="hljs-keyword">switch</span> (exception)
            {
                <span class="hljs-keyword">case</span> BadHttpRequestException:
                    errorResponse.StatusCode = (<span class="hljs-keyword">int</span>)HttpStatusCode.BadRequest;
                    <span class="hljs-keyword">break</span>;

                <span class="hljs-keyword">case</span> NoBookFoundException:
                <span class="hljs-keyword">case</span> BookDoesNotExistException:
                    errorResponse.StatusCode = (<span class="hljs-keyword">int</span>)HttpStatusCode.NotFound;
                    <span class="hljs-keyword">break</span>;

                <span class="hljs-keyword">default</span>:
                    errorResponse.StatusCode = (<span class="hljs-keyword">int</span>)HttpStatusCode.InternalServerError;
                    <span class="hljs-keyword">break</span>;
            }

            <span class="hljs-comment">// Set the response status code</span>
            httpContext.Response.StatusCode = errorResponse.StatusCode;

            <span class="hljs-comment">// Write the error response as JSON</span>
            <span class="hljs-keyword">await</span> httpContext.Response.WriteAsJsonAsync(errorResponse, cancellationToken);

            <span class="hljs-comment">// Return true to indicate that the exception was handled</span>
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
    }
}
</code></pre>
<p>Let's break down the code above:</p>
<ul>
<li><p>We define a class named <code>GlobalExceptionHandler</code> that implements the <code>IExceptionHandler</code> interface. The <code>IExceptionHandler</code> interface is used to handle exceptions globally in the application.</p>
</li>
<li><p>The <code>GlobalExceptionHandler</code> class contains a constructor that initializes the <code>ILogger&lt;GlobalExceptionHandler&gt;</code> dependency. The <code>ILogger</code> is used for logging information and errors.</p>
</li>
<li><p>The <code>TryHandleAsync</code> method is used to handle exceptions asynchronously. This method accepts the <code>HttpContext</code>, <code>Exception</code>, and <code>CancellationToken</code> as parameters.</p>
</li>
<li><p>We log the exception details using the <code>ILogger</code> dependency.</p>
</li>
<li><p>We create an <code>ErrorResponse</code> object to represent the error response returned by the API. The <code>ErrorResponse</code> object contains the error message, title, and status code.</p>
</li>
<li><p>We determine the status code based on the type of exception. If the exception is a <code>BadHttpRequestException</code>, we set the status code to <code>BadRequest</code>. If the exception is a <code>NoBookFoundException</code> or <code>BookDoesNotExistException</code>, we set the status code to <code>NotFound</code>. Otherwise, we set the status code to <code>InternalServerError</code>.</p>
</li>
<li><p>We set the response status code using the <code>httpContext.Response.StatusCode</code> property.</p>
</li>
<li><p>We write the error response as JSON using the <code>httpContext.Response.WriteAsJsonAsync</code> method.</p>
</li>
<li><p>We return <code>true</code> to indicate that the exception was handled successfully.</p>
</li>
</ul>
<p>Now that we have created the exception classes, let's register the <code>GlobalExceptionHandler</code> in the dependency injection container. Since we created an Extension method for registering services in the dependency injection container, we will add the <code>GlobalExceptionHandler</code> to the <code>ServiceExtensions</code> class.</p>
<p>Update the <code>ServiceExtensions</code> class in the <code>Extensions</code> folder as follows:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Extensions/ServiceExtensions.cs</span>
<span class="hljs-comment">//...</span>
builder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;();

builder.Services.AddProblemDetails();

<span class="hljs-comment">//...</span>
</code></pre>
<p>The <code>AddExceptionHandler</code> method registers the <code>GlobalExceptionHandler</code> in the dependency injection container. The <code>AddProblemDetails</code> method registers the <code>ProblemDetails</code> class in the dependency injection container.</p>
<p>Now that we have registered the <code>GlobalExceptionHandler</code> in the dependency injection container, we can use it to handle exceptions globally in our application. In the next section, we will create the API endpoints to interact with the book data.</p>
<h2 id="heading-how-to-create-the-api-endpoints">How to Create the API Endpoints</h2>
<p>In the context of minimal APIs in ASP.NET Core, there are many ways to set up your endpoints.</p>
<p>You can define them directly in your <code>Program.cs</code> file. But as your project grows and you need to add more endpoints or functionality, it’s helpful to organize your code better. One way to achieve this is by creating a separate class to handle all the endpoints.</p>
<p>As we’ve discussed above, minimal APIs don’t use controllers or views like traditional ASP.NET Core applications. Instead, they use methods such as <code>MapGet</code>, <code>MapPost</code>, <code>MapPut</code>, and <code>MapDelete</code> to define HTTP methods and routes for API endpoints.</p>
<p>To get started, navigate to the <code>Endpoints</code> folder and create a new file named <code>BookEndpoints.cs</code>. Add the following code to the file:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Endpoints/BookEndpoints.cs</span>



<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Endpoints</span>
{
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookEndPoint</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IEndpointRouteBuilder <span class="hljs-title">MapBookEndPoint</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> IEndpointRouteBuilder app</span>)</span>
        {


            <span class="hljs-keyword">return</span> app;
        }
    }
}
</code></pre>
<p>The <code>BookEndpoints</code> class contains a <code>MapBookEndPoint</code> method that returns an <code>IEndpointRouteBuilder</code> object. The <code>IEndpointRouteBuilder</code> object is used to define the HTTP methods and routes for the API endpoints. In the next sections, we will define the API endpoints for <code>creating</code>, <code>reading</code>, <code>updating</code>, and <code>deleting</code> books.</p>
<h3 id="heading-how-to-create-the-addbookasync-books-endpoint">How to Create the <code>AddBookAsync</code> Books Endpoint</h3>
<p>In this section, we will create the <code>AddBookAsync</code> endpoint. This endpoint will accept a <code>Book</code> object as a JSON payload and add it to the database. We will use the <code>MapPost</code> method to define the HTTP method and route for this endpoint.</p>
<p>Add the following code to the <code>BookEndpoints</code> class:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Endpoints/BookEndpoints.cs</span>


<span class="hljs-comment">//...</span>
   <span class="hljs-comment">// Endpoint to add a new book</span>
      app.MapPost(<span class="hljs-string">"/books"</span>, <span class="hljs-keyword">async</span> (CreateBookRequest createBookRequest, IBookService bookService) =&gt;
        {
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.AddBookAsync(createBookRequest);
        <span class="hljs-keyword">return</span> Results.Created(<span class="hljs-string">$"/books/<span class="hljs-subst">{result.Id}</span>"</span>, result); 
        });


<span class="hljs-comment">//...</span>
</code></pre>
<ul>
<li><p><strong>Route Definition</strong>: The MapPost method defines the route for the endpoint as <code>/books</code>.</p>
</li>
<li><p><strong>Request Model</strong>: The endpoint accepts an <code>CreateBookRequest</code> object as a JSON payload. The <code>CreateBookRequest</code> object contains the data required to create a new book.</p>
</li>
<li><p><strong>Response Model</strong>: The endpoint returns a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data for the newly created book.</p>
</li>
<li><p><strong>Return Value</strong>: The endpoint returns a <code>Created</code> result. The <code>Created</code> result contains the location of the newly created book and the <code>Book</code> object.</p>
</li>
</ul>
<h3 id="heading-how-to-create-the-getbookasync-book-endpoint">How to Create the <code>GetBookAsync</code> Book Endpoint</h3>
<p>In this section, we will create the <code>GetBookAsync</code> endpoint. This endpoint will accept a book ID as a query parameter and return the book with the specified ID. We will use the <code>MapGet</code> method to define the HTTP method and route for this endpoint.</p>
<p>Add the following code to the <code>BookEndpoints</code> class:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Endpoints/BookEndpoints.cs</span>

<span class="hljs-comment">// ...</span>
    <span class="hljs-comment">// Endpoint to get all books</span>
    app.MapGet(<span class="hljs-string">"/books"</span>, <span class="hljs-keyword">async</span> (IBookService bookService) =&gt;
     {
    <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.GetBooksAsync();
    <span class="hljs-keyword">return</span> Results.Ok(result);
});


<span class="hljs-comment">//...</span>
</code></pre>
<ul>
<li><p><strong>Route Definition</strong>: The MapGet method defines the route for the endpoint as <code>/books</code>.</p>
</li>
<li><p><strong>Request Model</strong>: The endpoint accepts a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data required to create a new book.</p>
</li>
<li><p><strong>Response Model</strong>: The endpoint returns a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data for the newly created book.</p>
</li>
<li><p><strong>Return Value</strong>: The endpoint returns an <code>Ok</code> result. The <code>Ok</code> result contains the <code>Book</code> object.</p>
</li>
</ul>
<h3 id="heading-how-to-create-the-getbookbyidasync-book-endpoint">How to Create the <code>GetBookByIdAsync</code> Book Endpoint</h3>
<p>In this section, we will create the <code>GetBookByIdAsync</code> endpoint. This endpoint will accept a book ID as a route parameter and return the book with the specified ID. We will use the <code>MapGet</code> method to define the HTTP method and route for this endpoint.</p>
<p>Add the following code to the <code>BookEndpoints</code> class:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Endpoints/BookEndpoints.cs</span>
<span class="hljs-comment">//...</span>
<span class="hljs-comment">// Endpoint to get a book by ID</span>

  app.MapGet(<span class="hljs-string">"/books/{id:guid}"</span>, <span class="hljs-keyword">async</span> (Guid id, IBookService bookService) =&gt;
  {
    <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.GetBookByIdAsync(id);
    <span class="hljs-keyword">return</span> result != <span class="hljs-literal">null</span> ? Results.Ok(result) : Results.NotFound();
});

<span class="hljs-comment">//...</span>
</code></pre>
<ul>
<li><p><strong>Route Definition</strong>: The MapGet method defines the route for the endpoint as <code>/books/{id:guid}</code>. The <code>{id:guid}</code> parameter specifies that the <code>id</code> parameter should be a GUID.</p>
</li>
<li><p><strong>Request Model</strong>: The endpoint accepts a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data required to create a new book.</p>
</li>
<li><p><strong>Response Model</strong>: The endpoint returns a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data for the newly created book.</p>
</li>
<li><p><strong>Return Value</strong>: The endpoint returns an <code>Ok</code> result if the book is found. The <code>NotFound</code> result is returned if the book is not found.</p>
</li>
</ul>
<h3 id="heading-how-to-create-the-updatebookasync-book-endpoint">How to Create the <code>UpdateBookAsync</code> Book Endpoint</h3>
<p>In this section, we will create the <code>UpdateBookAsync</code> endpoint. This endpoint will accept a book ID as a route parameter and an <code>Book</code> object as a JSON payload and update the book with the specified ID. We will use the <code>MapPut</code> method to define the HTTP method and route for this endpoint.</p>
<p>Add the following code to the <code>BookEndpoints</code> class:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Endpoints/BookEndpoints.cs</span>

<span class="hljs-comment">//...</span>
   <span class="hljs-comment">// Endpoint to update a book by ID</span>
    app.MapPut(<span class="hljs-string">"/books/{id:guid}"</span>, <span class="hljs-keyword">async</span> (Guid id, UpdateBookRequest updateBookRequest, IBookService bookService) =&gt;
 {
<span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.UpdateBookAsync(id, updateBookRequest);
<span class="hljs-keyword">return</span> result != <span class="hljs-literal">null</span> ? Results.Ok(result) : Results.NotFound();
});

<span class="hljs-comment">//...</span>
</code></pre>
<ul>
<li><p><strong>Route Definition</strong>: The MapPut method defines the route for the endpoint as <code>/books/{id:guid}</code>. The <code>{id:guid}</code> parameter specifies that the <code>id</code> parameter should be a GUID.</p>
</li>
<li><p><strong>Request Model</strong>: The endpoint accepts a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data required to create a new book.</p>
</li>
<li><p><strong>Response Model</strong>: The endpoint returns a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data for the newly created book.</p>
</li>
<li><p><strong>Return Value</strong>: The endpoint returns an <code>Ok</code> result if the book is found. The <code>NotFound</code> result is returned if the book is not found.</p>
</li>
</ul>
<h3 id="heading-how-to-create-the-deletebookasync-book-endpoint">How to Create the <code>DeleteBookAsync</code> Book Endpoint</h3>
<p>In this section, we will create the <code>DeleteBookAsync</code> endpoint. This endpoint will accept a book ID as a route parameter and delete the book with the specified ID. We will use the <code>MapDelete</code> method to define the HTTP method and route for this endpoint.</p>
<p>Add the following code to the <code>BookEndpoints</code> class:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Endpoints/BookEndpoints.cs</span>

<span class="hljs-comment">//...</span>
   <span class="hljs-comment">// Endpoint to delete a book by ID</span>
 app.MapDelete(<span class="hljs-string">"/books/{id:guid}"</span>, <span class="hljs-keyword">async</span> (Guid id, IBookService bookService) =&gt;
{
<span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.DeleteBookAsync(id);
   <span class="hljs-keyword">return</span> result ? Results.NoContent() : Results.NotFound();
});


<span class="hljs-comment">//...</span>
</code></pre>
<ul>
<li><p><strong>Route Definition</strong>: The MapDelete method defines the route for the endpoint as <code>/books/{id:guid}</code>. The <code>{id:guid}</code> parameter specifies that the <code>id</code> parameter should be a GUID.</p>
</li>
<li><p><strong>Request Model</strong>: The endpoint accepts a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data required to create a new book.</p>
</li>
<li><p><strong>Response Model</strong>: The endpoint returns a <code>Book</code> object as a JSON payload. The <code>Book</code> object contains the data for the newly created book.</p>
</li>
<li><p><strong>Return Value</strong>: The endpoint returns a <code>NoContent</code> result if the book is deleted successfully. The <code>NotFound</code> result is returned if the book is not found.</p>
</li>
</ul>
<p>Now we have defined all the methods for the book endpoints. So your endpoint class should look like this:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Endpoints/BookEndpoints.cs</span>
<span class="hljs-keyword">using</span> bookapi_minimal.Contracts;
<span class="hljs-keyword">using</span> bookapi_minimal.Interfaces;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Endpoints</span>
{
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookEndPoint</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IEndpointRouteBuilder <span class="hljs-title">MapBookEndPoint</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> IEndpointRouteBuilder app</span>)</span>
        {
            <span class="hljs-comment">// Define the endpoints</span>

            <span class="hljs-comment">// Endpoint to add a new book</span>
            app.MapPost(<span class="hljs-string">"/books"</span>, <span class="hljs-keyword">async</span> (CreateBookRequest createBookRequest, IBookService bookService) =&gt;
            {
                <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.AddBookAsync(createBookRequest);
                <span class="hljs-keyword">return</span> Results.Created(<span class="hljs-string">$"/books/<span class="hljs-subst">{result.Id}</span>"</span>, result); 
            });


               <span class="hljs-comment">// Endpoint to get all books</span>
            app.MapGet(<span class="hljs-string">"/books"</span>, <span class="hljs-keyword">async</span> (IBookService bookService) =&gt;
            {
                <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.GetBooksAsync();
                <span class="hljs-keyword">return</span> Results.Ok(result);
            });

            <span class="hljs-comment">// Endpoint to get a book by ID</span>
            app.MapGet(<span class="hljs-string">"/books/{id:guid}"</span>, <span class="hljs-keyword">async</span> (Guid id, IBookService bookService) =&gt;
            {
                <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.GetBookByIdAsync(id);
                <span class="hljs-keyword">return</span> result != <span class="hljs-literal">null</span> ? Results.Ok(result) : Results.NotFound();
            });


            <span class="hljs-comment">// Endpoint to update a book by ID</span>
            app.MapPut(<span class="hljs-string">"/books/{id:guid}"</span>, <span class="hljs-keyword">async</span> (Guid id, UpdateBookRequest updateBookRequest, IBookService bookService) =&gt;
            {
                <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.UpdateBookAsync(id, updateBookRequest);
                <span class="hljs-keyword">return</span> result != <span class="hljs-literal">null</span> ? Results.Ok(result) : Results.NotFound();
            });

            <span class="hljs-comment">// Endpoint to delete a book by ID</span>
            app.MapDelete(<span class="hljs-string">"/books/{id:guid}"</span>, <span class="hljs-keyword">async</span> (Guid id, IBookService bookService) =&gt;
            {
                <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> bookService.DeleteBookAsync(id);
                <span class="hljs-keyword">return</span> result ? Results.NoContent() : Results.NotFound();
            });

            <span class="hljs-keyword">return</span> app;
        }
    }
}
</code></pre>
<p>Congratulations! You have created all the endpoints for the book API. The endpoints handle the CRUD operations for books and return the appropriate responses based on the request and data.</p>
<h3 id="heading-how-to-register-the-endpoints">How to Register the Endpoints</h3>
<p>After defining the API endpoints for the book API, the next step is to register these endpoints in the <code>Program.cs</code> file. We will use the <code>MapBookEndpoints</code> method to register the book endpoints.</p>
<p>We should also clean up our <code>Program.cs</code> class to ensure it remains organized and maintainable.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Program.cs</span>

<span class="hljs-keyword">using</span> System.Reflection;
<span class="hljs-keyword">using</span> bookapi_minimal.Endpoints;
<span class="hljs-keyword">using</span> bookapi_minimal.Services;
<span class="hljs-keyword">using</span> Microsoft.OpenApi.Models;

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);


builder.AddApplicationServices();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c=&gt;
{
    c.SwaggerDoc(<span class="hljs-string">"v1"</span>, <span class="hljs-keyword">new</span> OpenApiInfo { Title = <span class="hljs-string">"Mimal API"</span>, Version = <span class="hljs-string">"v1"</span>, Description = <span class="hljs-string">"Showing how you can build minimal "</span> +
        <span class="hljs-string">"api with .net"</span> });


    <span class="hljs-comment">// Set the comments path for the Swagger JSON and UI.</span>
    <span class="hljs-keyword">var</span> xmlFile = <span class="hljs-string">$"<span class="hljs-subst">{Assembly.GetExecutingAssembly().GetName().Name}</span>.xml"</span>;
    <span class="hljs-keyword">var</span> xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    c.IncludeXmlComments(xmlPath);

});
<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Configure the HTTP request pipeline.</span>
<span class="hljs-keyword">if</span> (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseExceptionHandler();


app.MapGroup(<span class="hljs-string">"/api/v1/"</span>)
   .WithTags(<span class="hljs-string">" Book endpoints"</span>)
   .MapBookEndPoint();

app.Run();
</code></pre>
<p>Let's break down the key components of the <code>Program.cs</code> file:</p>
<ul>
<li><p><strong>AddApplicationServices</strong>: This method registers the necessary services for the API. It is an extension method we created earlier to add services to the dependency injection container.</p>
</li>
<li><p><strong>AddSwaggerGen</strong>: This method registers the Swagger generator, which is used to create the Swagger documentation for the API. We specify the title, version, and description of the API in the Swagger document.</p>
</li>
<li><p><strong>MapGroup</strong>: This method groups the endpoints. It takes a path as a parameter and returns an <code>IEndpointRouteBuilder</code> object. We use the <code>WithTags</code> method to add tags to the endpoints and the <code>MapBookEndpoints</code> method to register the book endpoints.</p>
</li>
<li><p><strong>Run</strong>: This method starts the application.</p>
</li>
</ul>
<p>To enable Swagger documentation, you need to add the <code>GenerateDocumentationFile</code> property to your <code>.csproj</code> file. In this example, the file is named <code>bookapi-minimal.csproj</code>, but the name may vary based on your project.</p>
<p>Add the following line to your <code>.csproj</code> file:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">GenerateDocumentationFile</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">GenerateDocumentationFile</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
</code></pre>
<p>By the end, bookapi-minimal.csproj should look like this:</p>
<pre><code class="lang-xml">
<span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk.Web"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net8.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Nullable</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">Nullable</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ImplicitUsings</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">ImplicitUsings</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">GenerateDocumentationFile</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">GenerateDocumentationFile</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RootNamespace</span>&gt;</span>bookapi_minimal<span class="hljs-tag">&lt;/<span class="hljs-name">RootNamespace</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"FluentValidation.DependencyInjectionExtensions"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"11.9.2"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.AspNetCore.OpenApi"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.6"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.Design"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">IncludeAssets</span>&gt;</span>runtime; build; native; contentfiles; analyzers; buildtransitive<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeAssets</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PrivateAssets</span>&gt;</span>all<span class="hljs-tag">&lt;/<span class="hljs-name">PrivateAssets</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">PackageReference</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.SqlServer"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.Tools"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.8"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">IncludeAssets</span>&gt;</span>runtime; build; native; contentfiles; analyzers; buildtransitive<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeAssets</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PrivateAssets</span>&gt;</span>all<span class="hljs-tag">&lt;/<span class="hljs-name">PrivateAssets</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">PackageReference</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Swashbuckle.AspNetCore"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"6.4.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>Now that we have registered the book endpoints in the <code>Program.cs</code> file, we can run the application and test the API endpoints using Swagger.</p>
<p>When you run the application, you should see the Swagger documentation at the following URL: <a target="_blank" href="https://localhost:5001/swagger/index.html"><code>https://localhost:5001/swagger/index.html</code></a>. The Swagger documentation provides information about the API endpoints, request and response models, and allows you to test the endpoints directly from the browser. You should see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732624213627/e1e3b3d1-2ecb-486a-b95b-28b958f52462.png" alt="Book API Endpoints Swagger UI " width="2474" height="1242" loading="lazy"></p>
<p>Congratulations! You have implemented the business logic for the book service, created custom exceptions, defined API endpoints, and registered the endpoints in the <code>Program.cs</code> file. You have also enabled Swagger documentation to test the API endpoints.</p>
<h2 id="heading-how-to-add-seed-data-to-the-database">How to Add Seed Data to the Database</h2>
<p>One more important step is to seed the database with initial data when the application starts. This seed data will populate the database, allowing you to test your API endpoints without manually adding data.</p>
<p>Let's add some seed data before performing migrations and testing our API endpoints.</p>
<p>To achieve this, we will create a new class in our Configuration folder called <code>BookTypeConfigurations</code> and add the following code:</p>
<pre><code class="lang-csharp">

<span class="hljs-keyword">using</span> bookapi_minimal.Models;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore.Metadata.Builders;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">bookapi_minimal.Configurations</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BookTypeConfigurations</span> : <span class="hljs-title">IEntityTypeConfiguration</span>&lt;<span class="hljs-title">BookModel</span>&gt;
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Configure</span>(<span class="hljs-params">EntityTypeBuilder&lt;BookModel&gt; builder</span>)</span>
        {
            <span class="hljs-comment">// Configure the table name</span>
            builder.ToTable(<span class="hljs-string">"Books"</span>);

            <span class="hljs-comment">// Configure the primary key</span>
            builder.HasKey(x =&gt; x.Id);

            <span class="hljs-comment">// Configure properties</span>
            builder.Property(x =&gt; x.Id).ValueGeneratedOnAdd();
            builder.Property(x =&gt; x.Title).IsRequired().HasMaxLength(<span class="hljs-number">100</span>);
            builder.Property(x =&gt; x.Author).IsRequired().HasMaxLength(<span class="hljs-number">100</span>);
            builder.Property(x =&gt; x.Description).IsRequired().HasMaxLength(<span class="hljs-number">500</span>);
            builder.Property(x =&gt; x.Category).IsRequired().HasMaxLength(<span class="hljs-number">100</span>);
            builder.Property(x =&gt; x.Language).IsRequired().HasMaxLength(<span class="hljs-number">50</span>);
            builder.Property(x =&gt; x.TotalPages).IsRequired();

            <span class="hljs-comment">// Seed data</span>
            builder.HasData(
                <span class="hljs-keyword">new</span> BookModel
                {
                    Id = Guid.NewGuid(),
                    Title = <span class="hljs-string">"The Alchemist"</span>,
                    Author = <span class="hljs-string">"Paulo Coelho"</span>,
                    Description = <span class="hljs-string">"The Alchemist follows the journey of an Andalusian shepherd"</span>,
                    Category = <span class="hljs-string">"Fiction"</span>,
                    Language = <span class="hljs-string">"English"</span>,
                    TotalPages = <span class="hljs-number">208</span>
                },
                <span class="hljs-keyword">new</span> BookModel
                {
                    Id = Guid.NewGuid(),
                    Title = <span class="hljs-string">"To Kill a Mockingbird"</span>,
                    Author = <span class="hljs-string">"Harper Lee"</span>,
                    Description = <span class="hljs-string">"A novel about the serious issues of rape and racial inequality."</span>,
                    Category = <span class="hljs-string">"Fiction"</span>,
                    Language = <span class="hljs-string">"English"</span>,
                    TotalPages = <span class="hljs-number">281</span>
                },
                <span class="hljs-keyword">new</span> BookModel
                {
                    Id = Guid.NewGuid(),
                    Title = <span class="hljs-string">"1984"</span>,
                    Author = <span class="hljs-string">"George Orwell"</span>,
                    Description = <span class="hljs-string">"A dystopian social science fiction novel and cautionary tale about the dangers of totalitarianism. "</span>,
                  Category = <span class="hljs-string">"Fiction"</span>,
                  Language = <span class="hljs-string">"English"</span>,
                  TotalPages = <span class="hljs-number">328</span>
                } 
            );
        }
    }
}
</code></pre>
<p>Let's break down the code above:</p>
<p>In Entity Framework Core, you can use the <code>IEntityTypeConfiguration</code> interface to configure the entity type and seed data for the database. The <code>BookTypeConfigurations</code> class implements the <code>IEntityTypeConfiguration&lt;BookModel&gt;</code> interface and provides the configuration for the <code>BookModel</code> entity.</p>
<ul>
<li><p><strong>Configure Method</strong>: This method is used to configure the <code>BookModel</code> entity type. It defines the table name, primary key, and properties for the <code>BookModel</code> entity.</p>
<ul>
<li><p><strong>Table Name</strong>: The <code>ToTable</code> method specifies the name of the table to be created in the database. In this case, the table name is set to "Books".</p>
</li>
<li><p><strong>Primary Key</strong>: The <code>HasKey</code> method specifies the primary key for the <code>BookModel</code> entity. The primary key is set to the <code>Id</code> property.</p>
</li>
<li><p><strong>Properties</strong>: The <code>Property</code> method configures the properties of the <code>BookModel</code> entity. It specifies the data type, length, and constraints for each property.</p>
</li>
</ul>
</li>
<li><p><strong>Seed Data</strong>: The <code>HasData</code> method seeds the database with initial data. It creates three <code>BookModel</code> objects with sample data for testing the API endpoints.</p>
</li>
</ul>
<p>Now that we have created the <code>BookTypeConfigurations</code> class, we need to register this configuration in the <code>ApplicationContext</code> class. This ensures that the configuration is applied when the database is created or migrated.</p>
<p>We’re finally almost ready to test our API. But before we do that, we need to perform migrations to create the database and apply the seed data.</p>
<p>Remember that we added our database connection string in the <code>appsettings.json</code> file? Now let's perform a migration and later update our database for the migration to take effect.</p>
<h2 id="heading-how-to-perform-a-migration">How to Perform a Migration</h2>
<p>Migrations allow you to update the database schema based on changes made to your model classes. In Entity Framework Core, you can use the <code>dotnet ef migrations add</code> command to create a new migration reflecting these changes.</p>
<p>To perform a migration, run the following command in the terminal:</p>
<pre><code class="lang-bash">dotnet ef migrations add InitialCreate
</code></pre>
<p>If the command is successful, you should see an output similar to this:</p>
<pre><code class="lang-bash">Build started...
Build succeeded.
Done. To undo this action, use <span class="hljs-string">'ef migrations remove'</span>
</code></pre>
<p>You will now see a new folder called <code>Migrations</code> in your project. This folder contains the migration files that were created based on the changes made to your model classes. These migration files include the SQL commands required to update the database schema.</p>
<h3 id="heading-how-to-update-the-database">How to Update the Database</h3>
<p>After creating the migration, you need to apply the migration to update the database schema. You can use the <code>dotnet ef database update</code> command to apply the migration and update the database. Make sure the SQL Server is running.</p>
<p>Run the following command in the terminal:</p>
<pre><code class="lang-bash">
dotnet ef database update
</code></pre>
<p>This will update the database schema based on the changes made to your model classes. Make sure there are no errors on your database connection string.</p>
<h2 id="heading-how-to-test-the-api-endpoints">How to Test the API Endpoints</h2>
<p>Now we can test our endpoints using Swagger. To do this, run the application by executing the following command in the terminal:</p>
<pre><code class="lang-bash">
dotnet run
</code></pre>
<p>This will run our application. You can open your browser and navigate to <a target="_blank" href="https://localhost:5001/swagger/index.html"><code>https://localhost:5001/swagger/index.html</code></a> to access the Swagger documentation. You should see a list of API endpoints, request and response models, and the ability to test the endpoints directly from the browser.</p>
<p>If your port number is different from <code>5001</code>, don't worry – it will still work. The port might change depending on the type of machine you're using, but it will still achieve the same result.</p>
<h3 id="heading-how-to-test-the-get-all-books-endpoint">How to Test the <code>Get All Books</code> Endpoint</h3>
<p>To test the <code>Get All Books</code> endpoint, follow these steps:</p>
<ol>
<li><p>In the Swagger documentation, click on the <code>GET /api/v1/books</code> endpoint.</p>
</li>
<li><p>Click the <code>Try it out</code> button.</p>
</li>
<li><p>Click the <code>Execute</code> button.</p>
</li>
</ol>
<p>This will send a request to the API to retrieve all the books in the database.</p>
<p>You should see the response from the API, which will include the list of books that were seeded in the database.</p>
<p>The image below shows the response from the API:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732624950148/b497bc8e-727a-43c9-910f-755b3b6f208b.png" alt="Get All Books Endpoint Swagger UI " width="2391" height="1277" loading="lazy"></p>
<h3 id="heading-how-to-test-the-get-book-by-id-endpoint">How to Test the <code>Get Book by ID</code> Endpoint</h3>
<p>To test the <code>Get Book by ID</code> endpoint, follow these steps:</p>
<ol>
<li><p>In the Swagger documentation, click on the <code>GET /api/v1/books/{id}</code> endpoint.</p>
</li>
<li><p>Enter the ID of a book in the <code>id</code> field. You can use one of the book IDs that was seeded in the database.</p>
</li>
<li><p>Click the <code>Try it out</code> button.</p>
</li>
</ol>
<p>This will send a request to the API to retrieve the book with the specified ID. You should see the response from the API, which will include the book with the specified ID.</p>
<p>The image below shows the response from the API:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732625042363/fe356453-afa6-4a78-b963-d0befff7bd63.png" alt="Get Book By ID Endpoint Swagger UI " width="2342" height="1282" loading="lazy"></p>
<h3 id="heading-how-to-test-the-add-book-endpoint">How to Test the <code>Add Book</code> Endpoint</h3>
<p>To test the <code>Add Book</code> endpoint, follow these steps:</p>
<ol>
<li><p>In the Swagger documentation, click on the <code>POST /api/v1/books</code> endpoint.</p>
</li>
<li><p>Click the <code>Try it out</code> button.</p>
</li>
<li><p>Enter the book details in the request body.</p>
</li>
<li><p>Click the <code>Execute</code> button.</p>
</li>
</ol>
<p>This will send a request to the API to add a new book to the database.</p>
<p>You should see the response from the API, which will include the newly created book.</p>
<p>The image below shows the response from the API:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732625138350/faa54e57-e560-49ac-976a-b074e8eebb13.png" alt="Add Book Endpoint Swagger UI " width="2322" height="1292" loading="lazy"></p>
<h3 id="heading-how-to-test-the-update-book-endpoint">How to Test the <code>Update Book</code> Endpoint</h3>
<p>To test the <code>Update Book</code> endpoint, follow these steps:</p>
<ol>
<li><p>In the Swagger documentation, click on the <code>PUT /api/v1/books/{id}</code> endpoint.</p>
</li>
<li><p>Enter the ID of a book in the <code>id</code> field. You can use the id of one of the books that we just added.</p>
</li>
<li><p>Click the <code>Try it out</code> button.</p>
</li>
</ol>
<p>This will send a request to the API to update the book with the specified ID.</p>
<p>You should see the response from the API, which will include the updated book.</p>
<p>The image below shows the response from the API:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732625300781/3de90d6c-92ca-40cb-a54e-2236ec921d86.png" alt="Update Book Endpoint Swagger UI " width="2326" height="1311" loading="lazy"></p>
<h3 id="heading-how-to-test-the-delete-book-endpoint">How to Test the <code>Delete Book</code> Endpoint</h3>
<p>To test the <code>Delete Book</code> endpoint, follow these steps:</p>
<ol>
<li><p>In the Swagger documentation, click on the <code>DELETE /api/v1/books/{id}</code> endpoint.</p>
</li>
<li><p>Enter the ID of a book in the <code>id</code> field. You can use any of the ids from the books that we just added or the seeded data.</p>
</li>
<li><p>Click the <code>Try it out</code> button.</p>
</li>
</ol>
<p>This will send a request to the API to delete the book with the specified ID.</p>
<p>The image below shows the response from the API:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732625225432/3b066f4c-2bf2-4f0c-a104-a94dbbad1706.png" alt="Delete Book Endpoint Swagger UI " width="2327" height="1270" loading="lazy"></p>
<p>Congratulations! You have implemented all the CRUD operations for books and tested the API endpoints using Swagger, verifying that they work as expected. You can now build on this foundation to add more features and functionality to your API.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This handbook explored how to create a minimal API in ASP.NET Core with .NET 8. We built a comprehensive book API that supports CRUD operations, implemented custom exceptions, defined and registered API endpoints, and enabled Swagger documentation for easy testing.</p>
<p>Following this tutorial, you have gained a solid foundation for building minimal APIs with ASP.NET Core. You can now apply this knowledge and create robust APIs for various domains and industries.</p>
<p>I hope you found this tutorial both helpful and informative. Thank you for reading!</p>
<p>Feel free to connect with me on social media:</p>
<ul>
<li><p><a target="_blank" href="https://x.com/Clifftech_Dev">Twitter</a></p>
</li>
<li><p><a target="_blank" href="https://www.linkedin.com/in/isaiah-clifford-opoku/">LinkedIn</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/Clifftech123">GitHub</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
