<?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" version="2.0"><channel><title><![CDATA[Code With Renato]]></title><description><![CDATA[Code With Renato]]></description><link>https://codewithrenato.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 10:35:41 GMT</lastBuildDate><atom:link href="https://codewithrenato.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why ASP.NET Core and .NET Aspire Are a Game-Changer for Developers]]></title><description><![CDATA[Unlocking Full-Stack Potential: How ASP.NET Core and .NET Aspire are Revolutionizing Development
In the fast-paced world of software development, building robust, scalable, and maintainable full-stack applications often feels like navigating a labyri...]]></description><link>https://codewithrenato.com/why-aspnet-core-and-net-aspire-are-a-game-changer-for-developers</link><guid isPermaLink="true">https://codewithrenato.com/why-aspnet-core-and-net-aspire-are-a-game-changer-for-developers</guid><category><![CDATA[.NET]]></category><category><![CDATA[C#]]></category><category><![CDATA[.net aspire]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Tue, 12 Aug 2025 13:45:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755006291211/7ab4d399-d30e-4ad8-933d-3d5c9ef4f89c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-unlocking-full-stack-potential-how-aspnethttpaspnet-core-and-net-aspire-are-revolutionizing-development"><strong>Unlocking Full-Stack Potential: How</strong> <a target="_blank" href="http://ASP.NET"><strong>ASP.NET</strong></a> <strong>Core and .NET Aspire are Revolutionizing Development</strong></h2>
<p>In the fast-paced world of software development, building robust, scalable, and maintainable full-stack applications often feels like navigating a labyrinth. From backend APIs and databases to frontend frameworks and deployment pipelines, the sheer complexity can be daunting. But what if there was a way to significantly streamline this process, making distributed application development not just manageable, but genuinely enjoyable?</p>
<p>Enter <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core and the exciting innovation that is .NET Aspire. This powerful combination is rapidly changing how we approach full-stack development, especially for us .NET and C# enthusiasts. Recently, I came across an excellent guide on building a full-stack React app with an <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core Web API and Aspire, and it really solidified my belief in the direction Microsoft is taking with our ecosystem. It's time to dive into why this matters and how you can leverage these tools to elevate your development workflow.</p>
<hr />
<h3 id="heading-the-power-duo-aspnethttpaspnet-core-and-net-aspire"><strong>The Power Duo:</strong> <a target="_blank" href="http://ASP.NET"><strong>ASP.NET</strong></a> <strong>Core and .NET Aspire</strong></h3>
<p>The core of modern web development in .NET has been <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core for years a high-performance, cross-platform framework for building cloud-based, modern internet-connected applications. It's flexible, fast, and constantly evolving. However, even with <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core's strengths, orchestrating multiple services, databases, and frontend applications in a local development environment, let alone for deployment, still presented significant challenges.</p>
<p>This is precisely where .NET Aspire steps in. .NET Aspire is an opinionated, cloud-native stack for building observable, production-ready distributed applications. Think of it as your intelligent orchestrator and development helper for complex application topologies. It takes the pain out of managing disparate services, database connections, and frontend integration during local development, providing a cohesive and observable experience from day one. The recent focus on integrating Aspire seamlessly with front-end technologies like React, as highlighted in the community's recent step-by-step guides, is a game-changer. It means we can now build interconnected systems with an unmatched level of local development ease.</p>
<hr />
<h3 id="heading-why-this-matters-for-netc-developers"><strong>Why This Matters for .NET/C# Developers</strong></h3>
<p>For seasoned .NET and C# developers, Aspire isn't just another library; it's a paradigm shift for distributed application development. Here’s why it’s a big deal:</p>
<ul>
<li><p><strong>Simplified Local Development:</strong> Gone are the days of wrestling with Docker Compose files or manually starting multiple services. Aspire's AppHost project orchestrates everything. Spin up your backend API, database, and frontend with a single command, and they all "just work" together. This drastically reduces setup time and cognitive load.</p>
</li>
<li><p><strong>Enhanced Observability Out-of-the-Box:</strong> Aspire provides a brilliant interactive dashboard that offers real-time insights into your application's health, logs, traces, and metrics. This centralized view for all your distributed components (<a target="_blank" href="http://ASP.NET">ASP.NET</a> Core APIs, databases, message queues, and even your React app!) is invaluable for debugging and understanding system behavior, especially when things go awry.</p>
</li>
<li><p><strong>Seamless Frontend Integration:</strong> Integrating a React, Angular, or Vue.js frontend with your <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core backend can often involve tricky proxy configurations and environment variable management. Aspire simplifies this by providing dedicated hosting integrations (like <code>builder.AddViteApp</code> for React/Vite), allowing your frontend to discover and communicate with your backend services automatically.</p>
</li>
<li><p><strong>Production-Ready Foundations:</strong> Aspire isn't just for local dev. Its design philosophy extends to deployment, providing patterns and integrations that make your distributed applications easier to deploy to cloud environments like Azure Container Apps. This consistency from development to production is a huge win for reliability and developer confidence.</p>
</li>
<li><p><strong>Accelerated Development Cycle:</strong> By abstracting away much of the boilerplate and configuration, Aspire allows you to focus on writing business logic. This increased velocity means features get built faster, and developers spend less time on infrastructure plumbing.</p>
</li>
</ul>
<hr />
<h3 id="heading-practical-application-building-a-todo-app-with-aspire-aspnethttpaspnet-core-and-react"><strong>Practical Application: Building a Todo App with Aspire,</strong> <a target="_blank" href="http://ASP.NET"><strong>ASP.NET</strong></a> <strong>Core, and React</strong></h3>
<p>Let's illustrate with a common scenario: building a simple Todo application. Traditionally, this might involve setting up an <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core Web API, configuring a database (say, SQLite), and then setting up a React project that knows how to talk to that API. Each piece has its own setup, and getting them to communicate during local development can be fiddly.</p>
<p>With .NET Aspire, the workflow becomes remarkably smooth.</p>
<ol>
<li><strong>Start with Aspire:</strong> You begin by creating an Aspire Starter App. This gives you an AppHost project, which acts as the control plane for your distributed application.</li>
</ol>
<pre><code class="lang-csharp">dotnet <span class="hljs-keyword">new</span> aspire-starter -n MyTodoApp
cd MyTodoApp
</code></pre>
<ol start="2">
<li><a target="_blank" href="http://ASP.NET"><strong>ASP.NET</strong></a> <strong>Core Web API:</strong> You'd then add your <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core Web API project. What's neat here is how effortlessly Aspire integrates with Entity Framework Core and a database like SQLite. Instead of manually configuring connection strings everywhere, Aspire uses references:</li>
</ol>
<pre><code class="lang-csharp"><span class="hljs-comment">// In MyTodoApp.AppHost/Program.cs</span>
<span class="hljs-keyword">var</span> db = builder.AddSqlite(<span class="hljs-string">"tododb"</span>).WithSqliteWeb(); <span class="hljs-comment">// Aspire handles SQLite setup</span>
<span class="hljs-keyword">var</span> apiService = builder.AddProject&lt;Projects.MyTodoApp_ApiService&gt;(<span class="hljs-string">"apiservice"</span>)
    .WithReference(db) <span class="hljs-comment">// API service automatically gets the database connection</span>
    .WithHttpHealthCheck(<span class="hljs-string">"/health"</span>);
</code></pre>
<p>And in your API's <code>Program.cs</code>, you simply use <code>builder.AddSqliteDbContext</code> to hook up your <code>DbContext</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// In MyTodoApp.ApiService/Program.cs</span>
builder.AddSqliteDbContext&lt;TodoDbContext&gt;(<span class="hljs-string">"tododb"</span>);
</code></pre>
<p>This removes the need for explicit connection strings in <code>appsettings.json</code> for local development, simplifying configuration significantly. You can even use <code>dotnet scaffold</code> to quickly generate API endpoints for your models, further accelerating backend development.</p>
<ol start="3">
<li><strong>React Frontend Integration:</strong> This is where Aspire truly shines for full-stack developers. Adding a React app (using Vite, for instance) is integrated directly into your Aspire orchestration:</li>
</ol>
<pre><code class="lang-csharp">npm create vite@latest todo-frontend -- --template react
aspire <span class="hljs-keyword">add</span> nodejs <span class="hljs-comment">// Adds Aspire.Hosting.NodeJs to AppHost</span>
aspire <span class="hljs-keyword">add</span> ct-extensions <span class="hljs-comment">// Adds CommunityToolkit.Aspire.Hosting.NodeJS.Extensions for Vite support</span>
</code></pre>
<p>Then, in your <code>AppHost.cs</code>, you tell Aspire about your frontend:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// In MyTodoApp.AppHost/Program.cs</span>
builder.AddViteApp(name: <span class="hljs-string">"todo-frontend"</span>, workingDirectory: <span class="hljs-string">"../todo-frontend"</span>)
    .WithReference(apiService) <span class="hljs-comment">// Frontend knows about the API service</span>
    .WaitFor(apiService)
    .WithNpmPackageInstallation();
</code></pre>
<p>Crucially, <code>vite.config.js</code> in your React project can leverage environment variables injected by Aspire to proxy API requests, meaning your React app automatically knows where its backend lives without hardcoding URLs:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// In todo-frontend/vite.config.js</span>
import { defineConfig, loadEnv } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-function">export <span class="hljs-keyword">default</span> <span class="hljs-title">defineConfig</span>(<span class="hljs-params">({ mode }</span>)</span> =&gt; {
  <span class="hljs-keyword">const</span> env = loadEnv(mode, process.cwd(), <span class="hljs-string">''</span>);
  <span class="hljs-keyword">return</span> {
    <span class="hljs-comment">// ...</span>
    server: {
      port: parseInt(env.VITE_PORT),
      proxy: {
        <span class="hljs-string">'/api'</span>: {
          target: process.env.services__apiservice__https__0 || process.env.services__apiservice__http__0,
          changeOrigin: <span class="hljs-literal">true</span>,
          secure: <span class="hljs-literal">false</span>,
          rewrite: (path) =&gt; path.replace(/^\/api/, <span class="hljs-string">''</span>)
        }
      }
    },
    <span class="hljs-comment">// ...</span>
  }
})
</code></pre>
<p>With this setup, running your <code>AppHost</code> project brings up the entire distributed application – the <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core API, the SQLite database, and the React frontend – all interconnected and observable from the Aspire dashboard. This level of integrated development experience is a significant leap forward.</p>
<hr />
<h3 id="heading-my-take-the-future-of-full-stack-net-development"><strong>My Take: The Future of Full-Stack .NET Development</strong></h3>
<p>I've been building applications with .NET for years, and one consistent challenge in distributed systems has been the friction of local development setup and unified observability. Aspire directly addresses these pain points. It's not just about writing C# code; it's about making the <em>entire</em> development lifecycle smoother and more efficient.</p>
<p>For .NET developers who are either new to distributed systems or looking to modernize their existing architectures, Aspire provides a gentle on-ramp. It abstracts away much of the underlying complexity (like Docker networking or Kubernetes manifests for local dev) while still exposing the power of these technologies when you need it. This means you can focus on architecting your business logic and user experience, rather than spending hours debugging environment variables or service discovery issues.</p>
<p>Moreover, the built-in observability is a game-changer for debugging. Being able to see logs, traces, and metrics from every part of your application in a single, coherent dashboard provides a level of insight that was previously difficult to achieve without significant setup. It fosters a more proactive approach to problem-solving, allowing developers to quickly pinpoint issues across service boundaries.</p>
<p>This emphasis on developer experience, combined with the robustness of <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core and the flexibility of modern front-end frameworks, truly positions the .NET ecosystem as a top-tier choice for building complex, scalable, full-stack applications. It's an exciting time to be a .NET developer!</p>
<hr />
<h3 id="heading-key-takeaways-amp-call-to-action"><strong>Key Takeaways &amp; Call to Action</strong></h3>
<p>The journey towards building complex, distributed applications doesn't have to be a struggle. With <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core providing the solid foundation and .NET Aspire acting as your intelligent orchestrator, you gain:</p>
<ul>
<li><p><strong>Simplified local development of distributed applications.</strong></p>
</li>
<li><p><strong>Out-of-the-box observability via a centralized dashboard.</strong></p>
</li>
<li><p><strong>Seamless integration with popular frontend frameworks like React.</strong></p>
</li>
<li><p><strong>A clearer path from local development to cloud deployment.</strong></p>
</li>
</ul>
<p>I highly encourage you to explore .NET Aspire yourself. Dive into the documentation, try out the quickstarts, and experience the difference it makes in your daily workflow.</p>
<p><strong>What are your thoughts on .NET Aspire? Have you already integrated it into your projects, or are you planning to? Share your experiences, tips, and any challenges you've faced in the comments below! Let's continue this conversation and build something amazing together.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Required Parameters in C# 13: A Cleaner Way to Write Methods]]></title><description><![CDATA[C# 13, currently in preview with .NET 10, introduces a long-awaited feature: required method parameters. While the required keyword has been available for properties since C# 11. This new enhancement lets developers enforce method arguments more decl...]]></description><link>https://codewithrenato.com/required-parameters-in-c-13-a-cleaner-way-to-write-methods</link><guid isPermaLink="true">https://codewithrenato.com/required-parameters-in-c-13-a-cleaner-way-to-write-methods</guid><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Mon, 23 Jun 2025 07:00:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/eybM9n4yrpE/upload/2d65ccdfc3df6d74fe2864da4b73ba29.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>C# 13, currently in preview with .NET 10, introduces a long-awaited feature: <strong>required method parameters</strong>. While the <code>required</code> keyword has been available for properties since C# 11. This new enhancement lets developers enforce method arguments more declaratively, improving readability and reducing boilerplate validation code.</p>
<p>In this post, we'll explain required parameters, how they differ from optional and nullable ones, and how to start using them immediately.</p>
<hr />
<h2 id="heading-the-problem-with-traditional-method-parameters">The Problem with Traditional Method Parameters</h2>
<p>Traditionally, when a method expects non-null input, you’re forced to write boilerplate like this:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SendEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>? recipient</span>)</span>
{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(recipient))
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Recipient is required"</span>, <span class="hljs-keyword">nameof</span>(recipient));

    <span class="hljs-comment">// logic here</span>
}
</code></pre>
<p>Even with nullable annotations, the compiler won't enforce non-null at compile time unless you're using nullable reference types strictly.</p>
<hr />
<h2 id="heading-the-new-required-parameters">The New <code>required</code> Parameters</h2>
<p>C# 13 now lets you mark parameters as <code>required</code>, just like you do with properties. The compiler ensures they’re supplied at the call site.</p>
<pre><code class="lang-plaintext">public void SendEmail(required string recipient)
{
    // No need for null checks unless you're doing deeper validation
    Console.WriteLine($"Sending to {recipient}");
}
</code></pre>
<p>If the caller omits the parameter or passes <code>null</code>, the compiler throws an error — <strong>before runtime</strong>.</p>
<h3 id="heading-syntax">Syntax</h3>
<pre><code class="lang-plaintext">void MyMethod(required string name, int age)
</code></pre>
<ul>
<li><p>Only applies to reference types (currently).</p>
</li>
<li><p>Cannot be combined with <code>params</code> or <code>optional</code> parameters.</p>
</li>
<li><p>Works well with overloading and pattern-matching.</p>
</li>
</ul>
<hr />
<h2 id="heading-real-world-use-case">Real-World Use Case</h2>
<p>Suppose you're building an API layer:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">RegisterUser</span>(<span class="hljs-params">required <span class="hljs-keyword">string</span> username, required <span class="hljs-keyword">string</span> email</span>)</span>
{
    <span class="hljs-comment">// No need to write null/empty validation unless you're doing regex/email format checks</span>
    _logger.LogInformation(<span class="hljs-string">$"User: <span class="hljs-subst">{username}</span>, Email: <span class="hljs-subst">{email}</span>"</span>);
    <span class="hljs-keyword">return</span> Ok();
}
</code></pre>
<p>The controller is cleaner, and contract enforcement is built into the compiler.</p>
<hr />
<h2 id="heading-gotchas-and-edge-cases">Gotchas and Edge Cases</h2>
<ul>
<li><p><strong>Required is not yet compatible with value types</strong> (though they must be passed anyway).</p>
</li>
<li><p><strong>Default values are not allowed</strong> on required parameters.</p>
</li>
<li><p><strong>Refactoring existing methods</strong> to include <code>required</code> might break existing call sites.</p>
</li>
<li><p><strong>This is a preview feature</strong>, so you’ll need the latest .NET 10 SDK and enable preview features in your <code>.csproj</code>:</p>
</li>
</ul>
<pre><code class="lang-plaintext">&lt;LangVersion&gt;preview&lt;/LangVersion&gt;
</code></pre>
<hr />
<h2 id="heading-summary">Summary</h2>
<p>Required parameters in C# 13 are a simple but powerful addition that promotes cleaner, more self-documenting code. By shifting enforcement to compile time, they reduce runtime bugs and eliminate redundant checks.</p>
<p>If you're already using <code>required</code> for object properties, this method-level enhancement will feel like a natural progression.</p>
<hr />
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/dotnet/csharplang/issues/4936">C# 13 Language Proposal (GitHub)</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10">.NET 10 Preview Features</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required">Required Members in C#</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Getting Started with LocalStack: AWS Development Without the Cloud]]></title><description><![CDATA[Developing and testing AWS-based applications locally can be frustrating due to cost, latency, and connectivity issues. LocalStack solves this by providing a fully functional local AWS cloud stack so you can build and test your infrastructure offline...]]></description><link>https://codewithrenato.com/getting-started-with-localstack-aws-development-without-the-cloud</link><guid isPermaLink="true">https://codewithrenato.com/getting-started-with-localstack-aws-development-without-the-cloud</guid><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><category><![CDATA[localstack]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Fri, 20 Jun 2025 10:38:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750415879223/6a3f8a21-5ef3-4014-82ac-da1293eed96c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Developing and testing AWS-based applications locally can be frustrating due to cost, latency, and connectivity issues. <a target="_blank" href="https://www.localstack.cloud"><strong>LocalStack</strong></a> solves this by providing a fully functional local AWS cloud stack so you can build and test your infrastructure offline.</p>
<p>In this post, we'll cover:</p>
<ul>
<li><p>What LocalStack is</p>
</li>
<li><p>How to set it up</p>
</li>
<li><p>Common use cases</p>
</li>
<li><p>C#/.NET example with S3</p>
</li>
<li><p>Edge cases and gotchas</p>
</li>
</ul>
<h2 id="heading-what-is-localstack">What Is LocalStack?</h2>
<p>LocalStack is an open-source tool that emulates a wide range of AWS services on your local machine. It runs in Docker and supports services like:</p>
<ul>
<li>S3, Lambda, API Gateway, DynamoDB, SQS, SNS, IAM, CloudWatch, and more.</li>
</ul>
<p>With LocalStack, you can run your AWS infrastructure locally, making it easier to test without incurring costs or relying on cloud connectivity.</p>
<h2 id="heading-setting-up-localstack">Setting Up LocalStack</h2>
<h3 id="heading-requirements">Requirements</h3>
<ul>
<li><p>Docker is installed and running</p>
</li>
<li><p>Python &amp; pip (for LocalStack CLI)</p>
</li>
</ul>
<h3 id="heading-installation">Installation</h3>
<pre><code class="lang-csharp">pip install localstack
localstack start
</code></pre>
<p>Or using Docker Compose (recommended):</p>
<pre><code class="lang-csharp"><span class="hljs-meta"># docker-compose.yml</span>
version: <span class="hljs-string">'3.8'</span>
services:
  localstack:
    image: localstack/localstack
    ports:
      - <span class="hljs-string">"4566:4566"</span>  <span class="hljs-meta"># Gateway to all services</span>
      - <span class="hljs-string">"4571:4571"</span>
    environment:
      - SERVICES=s3,sqs,lambda
      - DEBUG=<span class="hljs-number">1</span>
      - DATA_DIR=/tmp/localstack/data
    volumes:
      - <span class="hljs-string">"/var/run/docker.sock:/var/run/docker.sock"</span>
      - <span class="hljs-string">"./.localstack:/tmp/localstack"</span>
</code></pre>
<p>Run:</p>
<pre><code class="lang-csharp">docker-compose up
</code></pre>
<hr />
<h2 id="heading-use-case-working-with-s3-in-net">Use Case: Working with S3 in .NET</h2>
<p>Install the AWS SDK:</p>
<pre><code class="lang-csharp">dotnet <span class="hljs-keyword">add</span> package AWSSDK.S3
</code></pre>
<h3 id="heading-s3-client-setup-in-c">S3 Client Setup in C</h3>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> s3Client = <span class="hljs-keyword">new</span> AmazonS3Client(
    <span class="hljs-keyword">new</span> BasicAWSCredentials(<span class="hljs-string">"test"</span>, <span class="hljs-string">"test"</span>),
    <span class="hljs-keyword">new</span> AmazonS3Config
    {
        ServiceURL = <span class="hljs-string">"http://localhost:4566"</span>,
        ForcePathStyle = <span class="hljs-literal">true</span>
    });
</code></pre>
<h3 id="heading-create-a-bucket-and-upload-a-file">Create a Bucket and Upload a File</h3>
<pre><code class="lang-csharp"><span class="hljs-keyword">await</span> s3Client.PutBucketAsync(<span class="hljs-string">"my-test-bucket"</span>);

<span class="hljs-keyword">await</span> s3Client.PutObjectAsync(<span class="hljs-keyword">new</span> PutObjectRequest
{
    BucketName = <span class="hljs-string">"my-test-bucket"</span>,
    Key = <span class="hljs-string">"hello.txt"</span>,
    ContentBody = <span class="hljs-string">"Hello from LocalStack"</span>
});
</code></pre>
<p>Verify with:</p>
<pre><code class="lang-csharp">aws --endpoint-url=http:<span class="hljs-comment">//localhost:4566 s3 ls s3://my-test-bucket</span>
</code></pre>
<hr />
<h2 id="heading-edge-cases-and-gotchas">Edge Cases and Gotchas</h2>
<ul>
<li><p><strong>Credentials</strong>: Use dummy credentials like <code>test/test</code> — LocalStack doesn’t validate them.</p>
</li>
<li><p><strong>Service URL</strong>: Always set <code>ServiceURL = "http://localhost:4566"</code>.</p>
</li>
<li><p><strong>ForcePathStyle</strong>: Required for S3 to avoid virtual host-style addressing.</p>
</li>
<li><p><strong>Persistence</strong>: Use <code>DATA_DIR</code> in Docker Compose to persist state across restarts.</p>
</li>
<li><p><strong>Feature Support</strong>: Not all AWS service features are supported (especially newer ones or deep integrations).</p>
</li>
</ul>
<h2 id="heading-summary">Summary</h2>
<p>LocalStack is a powerful tool for local AWS development. It cuts costs, increases speed, and allows testing even offline. Whether you're building with Lambda, API Gateway, or S3, LocalStack can significantly improve your dev workflow.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p>LocalStack Docs</p>
</li>
<li><p><a target="_blank" href="https://github.com/localstack/localstack">GitHub Repo</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/sdk-for-net/">AWS SDK for .NET</a></p>
</li>
<li><p><a target="_blank" href="https://docs.docker.com/compose/">Docker Compose</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[CI/CD Pipeline for .NET Lambdas Using GitHub Actions]]></title><description><![CDATA[Serverless applications in .NET are growing in popularity thanks to AWS Lambda’s support for .NET 6, .NET 7, and now .NET 8. But building, testing, and deploying these applications manually can be time-consuming and error-prone. That’s where CI/CD pi...]]></description><link>https://codewithrenato.com/cicd-pipeline-for-net-lambdas-using-github-actions</link><guid isPermaLink="true">https://codewithrenato.com/cicd-pipeline-for-net-lambdas-using-github-actions</guid><category><![CDATA[C#]]></category><category><![CDATA[AWS]]></category><category><![CDATA[lambda]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[GitHub Actions]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Tue, 10 Jun 2025 03:00:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xNdPWGJ6UCQ/upload/b3e4f83f2dd482f199bf8d209196b658.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Serverless applications in .NET are growing in popularity thanks to AWS Lambda’s support for .NET 6, .NET 7, and now .NET 8. But building, testing, and deploying these applications manually can be time-consuming and error-prone. That’s where CI/CD pipelines come in.</p>
<p>In this post, we’ll walk through how to set up a CI/CD pipeline using GitHub Actions to build, test, and deploy .NET AWS Lambda functions automatically.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along, you’ll need:</p>
<ul>
<li><p>A .NET Lambda project (for example, created with <code>dotnet new lambda.EmptyFunction</code>)</p>
</li>
<li><p>An AWS account with IAM credentials</p>
</li>
<li><p>AWS CLI installed locally (for initial setup)</p>
</li>
<li><p>An S3 bucket to store deployment artifacts (optional)</p>
</li>
<li><p>The AWS CLI and Amazon.Lambda.Tools installed:</p>
</li>
</ul>
<pre><code class="lang-bash">dotnet tool install -g Amazon.Lambda.Tools
</code></pre>
<h2 id="heading-project-structure">Project Structure</h2>
<p>Let’s assume the following structure:</p>
<pre><code class="lang-bash">MyLambdaFunction/
├── src/
│   └── MyLambdaFunction/
├── tests/
│   └── MyLambdaFunction.Tests/
├── .github/
│   └── workflows/
│       └── deploy.yml
</code></pre>
<hr />
<h2 id="heading-step-1-create-a-github-actions-workflow">Step 1: Create a GitHub Actions Workflow</h2>
<p>Create the file <code>.github/workflows/deploy.yml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI/CD</span> <span class="hljs-string">for</span> <span class="hljs-string">.NET</span> <span class="hljs-string">Lambda</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">main</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build-test-deploy:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">code</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v4</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-string">'8.x'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-restore</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">tests</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">test</span> <span class="hljs-string">--no-build</span> <span class="hljs-string">--verbosity</span> <span class="hljs-string">normal</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Get</span> <span class="hljs-string">Lambda</span> <span class="hljs-string">Role</span> <span class="hljs-string">ARN</span> <span class="hljs-string">from</span> <span class="hljs-string">SSM</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">ssm</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          ROLE_ARN=$(aws ssm get-parameter --name "/lambda/MyLambdaFunction/RoleArn" --query "Parameter.Value" --output text)
          echo "role_arn=$ROLE_ARN" &gt;&gt; $GITHUB_OUTPUT
</span>        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">AWS_ACCESS_KEY_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">AWS_SECRET_ACCESS_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">AWS_REGION:</span> <span class="hljs-string">us-east-1</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">AWS</span> <span class="hljs-string">Lambda</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">AWS_ACCESS_KEY_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_ACCESS_KEY_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">AWS_SECRET_ACCESS_KEY:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AWS_SECRET_ACCESS_KEY</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">AWS_REGION:</span> <span class="hljs-string">us-east-1</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          cd src/MyLambdaFunction
          dotnet lambda deploy-function MyLambdaFunction \
            --region $AWS_REGION \
            --function-role ${{ steps.ssm.outputs.role_arn }}</span>
</code></pre>
<hr />
<h2 id="heading-step-2-set-up-aws-credentials-for-github-actions">Step 2: Set Up AWS Credentials for GitHub Actions</h2>
<p>To securely authenticate your GitHub Actions workflow with AWS, you need to create an IAM user and store its access keys as GitHub secrets.</p>
<h3 id="heading-create-an-iam-user-in-aws">Create an IAM User in AWS</h3>
<ol>
<li><p>Sign in to the <a target="_blank" href="https://console.aws.amazon.com/">AWS Console</a></p>
</li>
<li><p>Go to IAM &gt; Users &gt; Add users</p>
</li>
<li><p>Choose a username (e.g., <code>github-actions-deploy</code>)</p>
</li>
<li><p>Enable <strong>Programmatic access</strong></p>
</li>
<li><p>Attach the <code>AWSLambdaFullAccess</code> policy or a custom policy with only necessary permissions</p>
</li>
<li><p>Save the <strong>Access Key ID</strong> and <strong>Secret Access Key</strong></p>
</li>
</ol>
<h3 id="heading-add-secrets-to-github">Add Secrets to GitHub</h3>
<ol>
<li><p>In your GitHub repo, go to <strong>Settings</strong> &gt; <strong>Secrets and variables</strong> &gt; <strong>Actions</strong></p>
</li>
<li><p>Add the following secrets:</p>
<ul>
<li><p><code>AWS_ACCESS_KEY_ID</code></p>
</li>
<li><p><code>AWS_SECRET_ACCESS_KEY</code></p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-step-3-use-ssm-parameters-to-avoid-manual-setup">Step 3: Use SSM Parameters to Avoid Manual Setup</h2>
<p>You can avoid hardcoding values like the Lambda role ARN in your workflow by storing them in AWS Systems Manager (SSM) Parameter Store.</p>
<h3 id="heading-store-required-values-in-ssm">Store Required Values in SSM</h3>
<p>Run this command locally to store the role ARN:</p>
<pre><code class="lang-bash">aws ssm put-parameter --name <span class="hljs-string">"/lambda/MyLambdaFunction/RoleArn"</span> \
  --<span class="hljs-built_in">type</span> String \
  --value <span class="hljs-string">"arn:aws:iam::&lt;account-id&gt;:role/&lt;lambda-execution-role&gt;"</span>
</code></pre>
<p>Your GitHub Actions workflow can then retrieve this value dynamically before deployment.</p>
<h2 id="heading-step-4-do-you-still-need-manual-deployment">Step 4: Do You Still Need Manual Deployment?</h2>
<p>Yes, <strong>unless</strong> you ensure everything required for the deployment is pre-configured. Here's why:</p>
<ul>
<li><p>The <code>dotnet lambda deploy-function</code> command creates the Lambda function if it doesn't exist, but requires details like the function role, memory size, etc.</p>
</li>
<li><p>If any required input is missing or the IAM role is misconfigured, the deployment will fail.</p>
</li>
<li><p>GitHub Actions is non-interactive, so any CLI prompts will break the pipeline.</p>
</li>
</ul>
<h3 id="heading-when-manual-first-time-deployment-is-needed">When Manual First-Time Deployment is Needed</h3>
<p>You should deploy manually the first time if:</p>
<ul>
<li><p>The Lambda function does not yet exist</p>
</li>
<li><p>You haven't scripted or automated the full configuration</p>
</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> src/MyLambdaFunction
dotnet lambda deploy-function MyLambdaFunction
</code></pre>
<p>This confirms that the function, role, region, and permissions are all set up correctly.</p>
<h3 id="heading-when-you-can-skip-manual-deployment">When You Can Skip Manual Deployment</h3>
<p>You can skip it if:</p>
<ul>
<li><p>The Lambda function and IAM role already exist</p>
</li>
<li><p>You’ve stored the function role in SSM and reference it in your workflow</p>
</li>
<li><p>You pass all required CLI arguments directly in the workflow</p>
</li>
</ul>
<p>This approach makes your pipeline truly hands-off from the beginning.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>With this setup:</p>
<ul>
<li><p>Every push to the <code>main</code> branch triggers your CI/CD pipeline</p>
</li>
<li><p>The pipeline builds, tests, and deploys your Lambda function</p>
</li>
<li><p>SSM parameters allow dynamic configuration without hardcoding</p>
</li>
<li><p>Manual setup can be avoided if resources are pre-provisioned</p>
</li>
</ul>
<p>This makes your Lambda deployments consistent, automated, and production-ready.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">AWS Lambda Tools for .NET CLI</a></p>
</li>
<li><p><a target="_blank" href="https://docs.github.com/en/actions">GitHub Actions Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html">AWS CLI Configuration</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html">AWS Systems Manager Parameter Store</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-cli.html">Deploying Lambda with dotnet CLI</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[.NET Aspire: A New Cloud-Native Application Model for .NET Developers]]></title><description><![CDATA[In the evolving world of distributed applications, .NET developers often face the challenges of stitching together multiple services, managing configurations, wiring dependencies, and ensuring observability across their systems. With the introduction...]]></description><link>https://codewithrenato.com/net-aspire-a-new-cloud-native-application-model-for-net-developers</link><guid isPermaLink="true">https://codewithrenato.com/net-aspire-a-new-cloud-native-application-model-for-net-developers</guid><category><![CDATA[.NET]]></category><category><![CDATA[Aspire ]]></category><category><![CDATA[C#]]></category><category><![CDATA[.net aspire]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Mon, 09 Jun 2025 10:46:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749465866648/e045709a-ca9d-4436-8873-78e0b95ae934.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the evolving world of distributed applications, .NET developers often face the challenges of stitching together multiple services, managing configurations, wiring dependencies, and ensuring observability across their systems. With the introduction of <strong>.NET Aspire</strong>, Microsoft brings a fresh approach to building and managing cloud-native applications with a strong emphasis on developer experience.</p>
<h2 id="heading-what-is-net-aspire">What is .NET Aspire?</h2>
<p>.NET Aspire is not a new framework, but an <strong>application model</strong> designed to simplify the development, orchestration, and observability of multi-service .NET applications. It provides tooling and conventions that make it easier to define, compose, and run cloud-native apps locally while remaining production-ready.</p>
<p>This model is especially relevant for developers working with:</p>
<ul>
<li><p>Microservices</p>
</li>
<li><p>Background worker services</p>
</li>
<li><p>APIs</p>
</li>
<li><p>Blazor frontends</p>
</li>
<li><p>Message-based systems</p>
</li>
</ul>
<h2 id="heading-getting-started-a-minimal-example">Getting Started: A Minimal Example</h2>
<p>Here is a basic example of how you can define an Aspire app host and include a simple web project and a Redis container:</p>
<pre><code class="lang-bash">var builder = DistributedApplication.CreateBuilder(args);

var redis = builder.AddRedisContainer(<span class="hljs-string">"cache"</span>);
builder.AddProject&lt;Projects.WebApp&gt;(<span class="hljs-string">"webapp"</span>)
       .WithReference(redis);

builder.Build().Run();
</code></pre>
<p>This short snippet defines a web application and a Redis container. The <code>.WithReference(redis)</code> call tells Aspire to inject the correct connection string and configure discovery between the services automatically.</p>
<h2 id="heading-key-advantages-of-net-aspire">Key Advantages of .NET Aspire</h2>
<h3 id="heading-local-first-cloud-ready">Local-First, Cloud-Ready</h3>
<p>.NET Aspire emphasizes a "local-first" development experience. You can run your entire application—including databases, background workers, and frontends—on your machine with minimal setup. Aspire ensures that the same structure can later be deployed to cloud environments like Kubernetes or Azure Container Apps.</p>
<h3 id="heading-built-in-service-support">Built-In Service Support</h3>
<p>Out of the box, Aspire integrates with common services such as:</p>
<ul>
<li><p>Redis</p>
</li>
<li><p>PostgreSQL</p>
</li>
<li><p>Dapr</p>
</li>
<li><p>YARP</p>
</li>
<li><p>OpenTelemetry</p>
</li>
</ul>
<p>These integrations come with sensible defaults, saving you from boilerplate configuration and custom orchestration code.</p>
<h3 id="heading-simplified-configuration-and-dependency-management">Simplified Configuration and Dependency Management</h3>
<p>Aspire uses declarative configuration to define how services relate to each other. This includes service discovery, environment variables, connection strings, and more—all managed through a central app host. This makes it easier to scale from development to production without rewriting how services are wired together.</p>
<h3 id="heading-observability-by-default">Observability by Default</h3>
<p>Instrumentation and observability are built in. With native OpenTelemetry support, Aspire enables distributed tracing, metrics, and logs across services without needing to manually configure each component. Developers also get access to a dashboard that provides real-time insights into service health and relationships.</p>
<h3 id="heading-developer-experience">Developer Experience</h3>
<p>.NET Aspire focuses on making the developer experience seamless:</p>
<ul>
<li><p>Fast local feedback loops</p>
</li>
<li><p>Integrated dashboards for diagnostics</p>
</li>
<li><p>Consistent tooling and project structure</p>
</li>
<li><p>Better onboarding for new team members</p>
</li>
</ul>
<h2 id="heading-when-to-use-net-aspire">When to Use .NET Aspire</h2>
<p>.NET Aspire is a strong choice when:</p>
<ul>
<li><p>You are building or modernizing a distributed system</p>
</li>
<li><p>Your app relies on multiple services that need to be wired and run together</p>
</li>
<li><p>You need consistent observability and diagnostics from development to production</p>
</li>
<li><p>You want to reduce infrastructure friction and boilerplate code</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>.NET Aspire is a welcome addition to the .NET ecosystem, offering practical tools and conventions for building modern cloud-native applications. It aligns well with developers who value clarity, scalability, and maintainability in distributed architectures.</p>
<p>If you're starting a new .NET project that spans multiple services or migrating an existing system to the cloud, Aspire is worth exploring.</p>
<h3 id="heading-resources"><strong>Resources:</strong></h3>
<ul>
<li><p>Official announcement: <a target="_blank" href="https://devblogs.microsoft.com/dotnet/introducing-dotnet-aspire">https://devblogs.microsoft.com/dotnet/introducing-dotnet-aspire</a></p>
</li>
<li><p>GitHub repo: <a target="_blank" href="https://github.com/dotnet/aspire">https://github.com/dotnet/aspire</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Authenticating API Gateway with Cognito in .NET Apps]]></title><description><![CDATA[When building secure and scalable APIs on AWS, Amazon Cognito paired with API Gateway provides a powerful combination for authentication. Cognito handles the identity layer, while API Gateway manages the request routing and enforcement of authenticat...]]></description><link>https://codewithrenato.com/authenticating-api-gateway-with-cognito-in-net-apps</link><guid isPermaLink="true">https://codewithrenato.com/authenticating-api-gateway-with-cognito-in-net-apps</guid><category><![CDATA[.NET]]></category><category><![CDATA[C#]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Cognito]]></category><category><![CDATA[aws-cognito]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Mon, 09 Jun 2025 09:00:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749379582398/e0402605-9a37-4ebd-9585-3e7bf5ef8e51.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When building secure and scalable APIs on AWS, Amazon Cognito paired with API Gateway provides a powerful combination for authentication. Cognito handles the identity layer, while API Gateway manages the request routing and enforcement of authentication via JWT tokens.</p>
<p>In this post, we’ll walk through how to authenticate .NET applications against an API Gateway endpoint secured by Cognito. This setup works great for web apps, desktop clients, and server-to-server communication.</p>
<h2 id="heading-why-use-cognito-with-api-gateway">Why Use Cognito with API Gateway?</h2>
<p>Amazon Cognito is a user directory service that provides:</p>
<ul>
<li><p>Secure user sign-up and sign-in</p>
</li>
<li><p>JWT token-based authentication</p>
</li>
<li><p>Integration with identity providers like Google, Facebook, and Microsoft</p>
</li>
<li><p>Federated access to AWS services</p>
</li>
</ul>
<p>API Gateway can directly validate JWT tokens issued by Cognito using a User Pool Authorizer. This allows you to secure your APIs without writing custom authentication logic.</p>
<h2 id="heading-step-1-set-up-cognito">Step 1: Set Up Cognito</h2>
<ol>
<li><p><strong>Create a User Pool</strong></p>
<ul>
<li><p>Go to the Cognito console.</p>
</li>
<li><p>Choose “Create user pool” and configure fields like username, email, and password policies.</p>
</li>
</ul>
</li>
<li><p><strong>Create an App Client</strong></p>
<ul>
<li><p>Disable the client secret (for public clients like desktop or mobile apps).</p>
</li>
<li><p>Enable the <code>ALLOW_USER_PASSWORD_AUTH</code> and <code>ALLOW_REFRESH_TOKEN_AUTH</code> flows if you want to authenticate with username/password directly.</p>
</li>
</ul>
</li>
<li><p><strong>(Optional) Hosted UI</strong></p>
<ul>
<li>Use Cognito's hosted UI if you want OAuth2-style login redirects.</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-step-2-create-and-configure-api-gateway">Step 2: Create and Configure API Gateway</h2>
<ol>
<li><p><strong>Create a REST API or HTTP API</strong></p>
<ul>
<li><p>In the API Gateway console, choose to create a new <strong>HTTP API</strong>.</p>
</li>
<li><p>Name it something like <code>UserServiceAPI</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Define a Secure Endpoint</strong></p>
<ul>
<li><p>Add a new route: <code>GET /profile</code></p>
</li>
<li><p>This endpoint will return user profile data and should only be accessible to authenticated users.</p>
</li>
</ul>
</li>
<li><p><strong>Add a Cognito Authorizer</strong></p>
<ul>
<li><p>Go to the <strong>Authorizers</strong> section.</p>
</li>
<li><p>Choose "Cognito" and select the user pool you created earlier.</p>
</li>
<li><p>Give the authorizer a name like <code>CognitoUserPoolAuthorizer</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Secure the</strong> <code>/profile</code> Route</p>
<ul>
<li><p>In the route settings, attach the Cognito authorizer to the <code>/profile</code> endpoint.</p>
</li>
<li><p>This ensures that any request to <code>/profile</code> must include a valid JWT token issued by your Cognito User Pool.</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-step-3-get-a-jwt-token-from-cognito-in-net">Step 3: Get a JWT Token from Cognito in .NET</h2>
<p>You can authenticate a user and retrieve tokens via HTTP using Cognito’s <code>/oauth2/token</code> endpoint.</p>
<p>Here’s how to do that using <code>HttpClient</code> in a .NET app:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> httpClient = <span class="hljs-keyword">new</span> HttpClient();

<span class="hljs-keyword">var</span> parameters = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>&gt;
{
    { <span class="hljs-string">"grant_type"</span>, <span class="hljs-string">"password"</span> },
    { <span class="hljs-string">"client_id"</span>, <span class="hljs-string">"&lt;your-app-client-id&gt;"</span> },
    { <span class="hljs-string">"username"</span>, <span class="hljs-string">"&lt;user-email&gt;"</span> },
    { <span class="hljs-string">"password"</span>, <span class="hljs-string">"&lt;user-password&gt;"</span> },
};

<span class="hljs-keyword">var</span> content = <span class="hljs-keyword">new</span> FormUrlEncodedContent(parameters);

<span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> httpClient.PostAsync(<span class="hljs-string">"https://&lt;your-domain&gt;.auth.&lt;region&gt;.amazoncognito.com/oauth2/token"</span>, content);
<span class="hljs-keyword">var</span> json = <span class="hljs-keyword">await</span> response.Content.ReadAsStringAsync();

<span class="hljs-keyword">var</span> tokenResponse = JsonSerializer.Deserialize&lt;TokenResponse&gt;(json);

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TokenResponse</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Access_token { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Id_token { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Refresh_token { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Token_type { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Expires_in { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<blockquote>
<p>Tip: You can also use MSAL.NET if you’re using the OAuth2 code flow with redirect URIs and Cognito’s hosted UI.</p>
</blockquote>
<h2 id="heading-step-4-send-authenticated-requests-to-api-gateway">Step 4: Send Authenticated Requests to API Gateway</h2>
<p>After obtaining the token from Cognito, use it to call the protected <code>/profile</code> endpoint:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> HttpClient();
client.DefaultRequestHeaders.Authorization = <span class="hljs-keyword">new</span> AuthenticationHeaderValue(<span class="hljs-string">"Bearer"</span>, tokenResponse.Id_token);

<span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> client.GetAsync(<span class="hljs-string">"https://your-api-id.execute-api.us-east-1.amazonaws.com/prod/profile"</span>);

<span class="hljs-keyword">if</span> (response.IsSuccessStatusCode)
{
    <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> response.Content.ReadAsStringAsync();
    Console.WriteLine(result);
}
<span class="hljs-keyword">else</span>
{
    Console.WriteLine(<span class="hljs-string">$"Request failed: <span class="hljs-subst">{response.StatusCode}</span>"</span>);
}
</code></pre>
<p>Replace <code>your-api-id</code> with your actual API Gateway ID and region. This example assumes the endpoint is deployed under the <code>prod</code> stage.</p>
<h2 id="heading-step-5-optional-validate-tokens-manually">Step 5: (Optional) Validate Tokens Manually</h2>
<p>If you’re processing the token yourself (e.g., in a Lambda function), use <code>System.IdentityModel.Tokens.Jwt</code> to parse and validate the token:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> handler = <span class="hljs-keyword">new</span> JwtSecurityTokenHandler();
<span class="hljs-keyword">var</span> token = handler.ReadJwtToken(tokenResponse.Id_token);

<span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> claim <span class="hljs-keyword">in</span> token.Claims)
{
    Console.WriteLine(<span class="hljs-string">$"<span class="hljs-subst">{claim.Type}</span>: <span class="hljs-subst">{claim.Value}</span>"</span>);
}
</code></pre>
<p>Make sure to validate:</p>
<ul>
<li><p>Signature (using the JWKS endpoint)</p>
</li>
<li><p>Expiration (<code>exp</code>)</p>
</li>
<li><p>Audience (<code>aud</code>)</p>
</li>
<li><p>Issuer (<code>iss</code>)</p>
</li>
</ul>
<h2 id="heading-common-issues">Common Issues</h2>
<ul>
<li><p><strong>Missing Token</strong>: Ensure the <code>Authorization</code> header is set and the route is protected with the authorizer.</p>
</li>
<li><p><strong>Invalid Token</strong>: Check that you're using the correct Cognito user pool and app client.</p>
</li>
<li><p><strong>CORS Errors</strong>: If calling from a browser, configure CORS settings in API Gateway.</p>
</li>
<li><p><strong>Token Expired</strong>: Use the refresh token to obtain a new access token.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>By combining Amazon Cognito and API Gateway, you can add strong authentication to your APIs without maintaining your own user system. .NET apps can easily acquire and use tokens to call these APIs securely. Whether you're building a mobile app, a web frontend, or a backend service, this setup is clean, scalable, and secure.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html">Amazon Cognito User Pools</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html">API Gateway Cognito Authorizer</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html">Cognito OAuth2 Token Endpoint</a></p>
</li>
<li><p><a target="_blank" href="https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/">System.IdentityModel.Tokens.Jwt</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Scheduling Tasks with CloudWatch and .NET Lambda]]></title><description><![CDATA[A common use case when working with AWS Lambda and .NET is running code on a schedule, similar to a cron job. AWS makes this easy with Amazon CloudWatch Events (now part of Amazon EventBridge), allowing you to repeatedly trigger Lambda functions.
In ...]]></description><link>https://codewithrenato.com/scheduling-tasks-with-cloudwatch-and-net-lambda</link><guid isPermaLink="true">https://codewithrenato.com/scheduling-tasks-with-cloudwatch-and-net-lambda</guid><category><![CDATA[AWS]]></category><category><![CDATA[C#]]></category><category><![CDATA[#CloudWatch]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Sat, 07 Jun 2025 10:21:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749291575891/c80749d4-cb54-49b3-b7b8-af02625938e4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A common use case when working with AWS Lambda and .NET is running code on a schedule, similar to a cron job. AWS makes this easy with <strong>Amazon CloudWatch Events</strong> (now part of <strong>Amazon EventBridge</strong>), allowing you to <strong>repeatedly trigger Lambda functions</strong>.</p>
<p>In this post, we’ll walk through how to schedule a .NET Lambda function using CloudWatch and how to structure your code for maintainability.</p>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you begin, make sure you have the following:</p>
<ul>
<li><p>An <strong>AWS Account</strong> with permission to create Lambda functions and CloudWatch rules</p>
</li>
<li><p>The <strong>.NET SDK</strong> is installed (preferably .NET 6 or later)</p>
</li>
<li><p>The <strong>AWS CLI</strong> is installed and configured (<code>aws configure</code>)</p>
</li>
<li><p><strong>Amazon.Lambda.Tools</strong> installed via the .NET CLI:</p>
<pre><code class="lang-bash">  dotnet tool install -g Amazon.Lambda.Tools
</code></pre>
</li>
<li><p>Basic familiarity with AWS Lambda and the .NET CLI</p>
</li>
</ul>
<hr />
<h2 id="heading-step-1-create-the-net-lambda-function">Step 1: Create the .NET Lambda Function</h2>
<p>You can use the AWS-provided templates with the <code>Amazon.Lambda.Templates</code> package.</p>
<pre><code class="lang-bash">dotnet new lambda.EmptyFunction --name ScheduledTaskLambda
<span class="hljs-built_in">cd</span> ScheduledTaskLambda
</code></pre>
<p>Edit the <code>Function.cs</code> file:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Function</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">FunctionHandler</span>(<span class="hljs-params">ILambdaContext context</span>)</span>
    {
        context.Logger.LogLine(<span class="hljs-string">"Scheduled task triggered at: "</span> + DateTime.UtcNow);

        <span class="hljs-comment">// Your logic here</span>
        <span class="hljs-keyword">await</span> DoScheduledWork();
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Task <span class="hljs-title">DoScheduledWork</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// Simulate a task like cleanup, email reminder, or sync</span>
        Console.WriteLine(<span class="hljs-string">"Running scheduled task..."</span>);
        <span class="hljs-keyword">return</span> Task.CompletedTask;
    }
}
</code></pre>
<hr />
<h2 id="heading-step-2-deploy-the-lambda-function">Step 2: Deploy the Lambda Function</h2>
<p>Deploy the Lambda:</p>
<pre><code class="lang-csharp">dotnet lambda deploy-function ScheduledTaskLambda
</code></pre>
<p>You’ll be prompted for AWS credentials and region during the deployment.</p>
<hr />
<h2 id="heading-step-3-schedule-with-cloudwatch-event-rule">Step 3: Schedule with CloudWatch Event Rule</h2>
<p>You can use the AWS Console or the AWS CLI:</p>
<pre><code class="lang-bash">aws events put-rule \
  --schedule-expression <span class="hljs-string">"rate(5 minutes)"</span> \
  --name MyScheduledLambdaRule
</code></pre>
<p>Add the Lambda target:</p>
<pre><code class="lang-bash">aws events put-targets \
  --rule MyScheduledLambdaRule \
  --targets <span class="hljs-string">"Id"</span>=<span class="hljs-string">"1"</span>,<span class="hljs-string">"Arn"</span>=<span class="hljs-string">"arn:aws:lambda:&lt;region&gt;:&lt;account-id&gt;:function:ScheduledTaskLambda"</span>
</code></pre>
<p>Grant CloudWatch permission to invoke the Lambda:</p>
<pre><code class="lang-bash">aws lambda add-permission \
  --function-name ScheduledTaskLambda \
  --statement-id MyScheduledEventPermission \
  --action <span class="hljs-string">'lambda:InvokeFunction'</span> \
  --principal events.amazonaws.com \
  --source-arn arn:aws:events:&lt;region&gt;:&lt;account-id&gt;:rule/MyScheduledLambdaRule
</code></pre>
<h2 id="heading-using-cron-expressions">Using Cron Expressions</h2>
<p>Instead of <code>rate()</code>You can use cron expressions:</p>
<pre><code class="lang-bash">--schedule-expression <span class="hljs-string">"cron(0 12 * * ? *)"</span>
</code></pre>
<p>This triggers at 12:00 PM UTC every day. AWS cron format:</p>
<pre><code class="lang-bash">cron(Minutes Hours Day-of-month Month Day-of-week Year)
</code></pre>
<h2 id="heading-real-world-example-sending-a-daily-email-report">Real-World Example: Sending a Daily Email Report</h2>
<p>Let’s say your system collects usage metrics or user activity throughout the day. Every morning at 8 AM UTC, you want to email a summary report to your team. You’ll:</p>
<ul>
<li><p>Query a database or analytics source</p>
</li>
<li><p>Generate a report, either plain text or HTML</p>
</li>
<li><p>Send the report using Amazon SES or another email provider</p>
</li>
</ul>
<p>This is a great use case for a scheduled Lambda function.</p>
<h3 id="heading-example-code">Example Code</h3>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Function</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">FunctionHandler</span>(<span class="hljs-params">ILambdaContext context</span>)</span>
    {
        <span class="hljs-keyword">var</span> report = <span class="hljs-keyword">await</span> GenerateReport();
        <span class="hljs-keyword">await</span> SendEmail(report);

        context.Logger.LogLine(<span class="hljs-string">"Daily report sent at: "</span> + DateTime.UtcNow);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">GenerateReport</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// Query a DB or metrics API. Simulated output:</span>
        <span class="hljs-keyword">return</span> Task.FromResult(<span class="hljs-string">"User signups: 42\nErrors: 3\nSales: $1280"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Task <span class="hljs-title">SendEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> report</span>)</span>
    {
        <span class="hljs-comment">// Integrate with Amazon SES, SendGrid, or another provider</span>
        Console.WriteLine(<span class="hljs-string">"Sending email with report:\n"</span> + report);
        <span class="hljs-keyword">return</span> Task.CompletedTask;
    }
}
</code></pre>
<p>This Lambda can be scheduled using the following cron expression:</p>
<pre><code class="lang-csharp">bashCopy code--schedule-expression <span class="hljs-string">"cron(0 8 * * ? *)"</span>
</code></pre>
<p>This means 8:00 AM UTC every day.</p>
<h2 id="heading-logging-and-monitoring">Logging and Monitoring</h2>
<ul>
<li><p>View logs in <strong>CloudWatch Logs</strong></p>
</li>
<li><p>Add custom logging using <code>context.Logger.LogLine(...)</code></p>
</li>
<li><p>Use structured logging libraries like <a target="_blank" href="https://serilog.net/">Serilog</a> for better insights</p>
</li>
</ul>
<h2 id="heading-summary">Summary</h2>
<p>With just a few commands and a bit of .NET code, you can schedule tasks using CloudWatch Events and AWS Lambda. This approach is scalable, cost-effective, and fully serverless.</p>
<h3 id="heading-benefits-recap">Benefits Recap:</h3>
<ul>
<li><p>No server maintenance</p>
</li>
<li><p>Pay only for executions</p>
</li>
<li><p>Integrated with the AWS ecosystem</p>
</li>
<li><p>Ideal for recurring jobs like cleanup, reporting, data sync, and reminders</p>
</li>
</ul>
<hr />
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html">AWS Lambda Developer Guide (.NET)</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html">Schedule expressions for rules - EventBridge</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">Amazon.Lambda.Tools GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduler-expressions.html">AWS Schedule Expressions</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Processing SQS Messages with .NET Lambda]]></title><description><![CDATA[Amazon SQS (Simple Queue Service) provides a robust mechanism for decoupling systems and processing asynchronous workloads. By combining it with AWS Lambda and .NET, you can build scalable, event-driven applications without managing infrastructure.
I...]]></description><link>https://codewithrenato.com/processing-sqs-messages-with-net-lambda</link><guid isPermaLink="true">https://codewithrenato.com/processing-sqs-messages-with-net-lambda</guid><category><![CDATA[.NET]]></category><category><![CDATA[AWS]]></category><category><![CDATA[SQS]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Fri, 06 Jun 2025 03:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748981115526/0bf26b00-5ac2-4528-9138-390feef1f1f1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Amazon SQS (Simple Queue Service) provides a robust mechanism for decoupling systems and processing asynchronous workloads. By combining it with AWS Lambda and .NET, you can build scalable, event-driven applications without managing infrastructure.</p>
<p>In this post, you'll learn how to write a .NET Lambda function that processes messages from an SQS queue.</p>
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<ol>
<li><p>An application pushes messages to an SQS queue.</p>
</li>
<li><p>AWS Lambda automatically polls the queue.</p>
</li>
<li><p>When messages arrive, Lambda invokes your function with a batch.</p>
</li>
<li><p>Your function processes each message.</p>
</li>
</ol>
<h2 id="heading-create-a-net-lambda-project">Create a .NET Lambda Project</h2>
<p>Start by creating a plain .NET project. You don’t need any special templates for this walkthrough.</p>
<pre><code class="lang-bash">dotnet new console -n SqsLambdaProcessor
<span class="hljs-built_in">cd</span> SqsLambdaProcessor
</code></pre>
<p>Add the necessary AWS Lambda packages:</p>
<pre><code class="lang-bash">dotnet add package Amazon.Lambda.Core
dotnet add package Amazon.Lambda.SQSEvents
dotnet add package Amazon.Lambda.Serialization.SystemTextJson
</code></pre>
<p>Rename <code>Program.cs</code> to <code>Function.cs</code>, or create a new <code>Function.cs</code> file, and define your handler:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Amazon.Lambda.SQSEvents;
<span class="hljs-keyword">using</span> Amazon.Lambda.Core;

[<span class="hljs-meta">assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))</span>]

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Function</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">FunctionHandler</span>(<span class="hljs-params">SQSEvent evnt, ILambdaContext context</span>)</span>
    {
        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> message <span class="hljs-keyword">in</span> evnt.Records)
        {
            <span class="hljs-keyword">await</span> ProcessMessageAsync(message, context);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Task <span class="hljs-title">ProcessMessageAsync</span>(<span class="hljs-params">SQSEvent.SQSMessage message, ILambdaContext context</span>)</span>
    {
        context.Logger.LogLine(<span class="hljs-string">$"Processing message <span class="hljs-subst">{message.MessageId}</span>: <span class="hljs-subst">{message.Body}</span>"</span>);

        <span class="hljs-comment">// Add your business logic here</span>

        <span class="hljs-keyword">return</span> Task.CompletedTask;
    }
}
</code></pre>
<h2 id="heading-deploy-the-lambda">Deploy the Lambda</h2>
<p>To deploy, you can use the AWS CLI, AWS Console, or the AWS Toolkit for Visual Studio. Here's how to deploy using the AWS CLI with the Lambda .NET tooling:</p>
<ol>
<li>Package the project:</li>
</ol>
<pre><code class="lang-bash">dotnet lambda package --output-package bin/release/sqs-lambda.zip
</code></pre>
<ol start="2">
<li>Deploy the function:</li>
</ol>
<pre><code class="lang-bash">aws lambda create-function \
  --function-name SqsLambdaProcessor \
  --runtime dotnet6 \
  --handler SqsLambdaProcessor::SqsLambdaProcessor.Function::FunctionHandler \
  --zip-file fileb://bin/release/sqs-lambda.zip \
  --role arn:aws:iam::YOUR_ACCOUNT_ID:role/YOUR_LAMBDA_EXECUTION_ROLE
</code></pre>
<p>Replace the role ARN with your actual IAM role that allows Lambda execution and access to SQS.</p>
<h2 id="heading-connect-the-sqs-queue">Connect the SQS Queue</h2>
<p>You can configure the SQS queue as an event source:</p>
<pre><code class="lang-bash">aws lambda create-event-source-mapping \
  --function-name SqsLambdaProcessor \
  --event-source-arn arn:aws:sqs:us-east-1:123456789012:your-queue-name \
  --batch-size 5
</code></pre>
<h2 id="heading-test-locally">Test Locally</h2>
<p>You can write unit tests using the <code>Amazon.Lambda.TestUtilities</code> package:</p>
<pre><code class="lang-bash">dotnet add package Amazon.Lambda.TestUtilities
</code></pre>
<p>Example test:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Fact</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">TestHandlerProcessesMessages</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> function = <span class="hljs-keyword">new</span> Function();
    <span class="hljs-keyword">var</span> context = <span class="hljs-keyword">new</span> TestLambdaContext();
    <span class="hljs-keyword">var</span> sqsEvent = <span class="hljs-keyword">new</span> SQSEvent
    {
        Records = <span class="hljs-keyword">new</span> List&lt;SQSEvent.SQSMessage&gt;
        {
            <span class="hljs-keyword">new</span> SQSEvent.SQSMessage { Body = <span class="hljs-string">"Test Message"</span> }
        }
    };

    <span class="hljs-keyword">await</span> function.FunctionHandler(sqsEvent, context);
}
</code></pre>
<h2 id="heading-error-handling-and-retries">Error Handling and Retries</h2>
<p>Lambda automatically retries failed messages. To handle failures:</p>
<ul>
<li><p>Implement try-catch inside <code>ProcessMessageAsync</code>.</p>
</li>
<li><p>Set up a Dead Letter Queue (DLQ) on your Lambda or SQS.</p>
</li>
<li><p>Use structured logging to track errors.</p>
</li>
</ul>
<h2 id="heading-best-practices">Best Practices</h2>
<ul>
<li><p>Make your function idempotent to handle retries safely.</p>
</li>
<li><p>Tune batch size to optimize throughput vs. memory usage.</p>
</li>
<li><p>Keep Lambda’s timeout below the SQS visibility timeout.</p>
</li>
<li><p>Log enough information to troubleshoot failures.</p>
</li>
</ul>
<h2 id="heading-cleanup">Cleanup</h2>
<p>To remove the resources:</p>
<pre><code class="lang-bash">aws lambda delete-function --function-name SqsLambdaProcessor
aws sqs delete-queue --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/your-queue-name
</code></pre>
<h3 id="heading-references">References</h3>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/sqs/">Amazon SQS</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html">AWS Lambda with SQS</a></p>
</li>
<li><p><a target="_blank" href="https://www.nuget.org/packages/Amazon.Lambda.SQSEvents">Amazon.Lambda.SQSEvents on NuGet</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">AWS .NET Lambda Tools GitHub</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Default Parameters in Lambda Expressions]]></title><description><![CDATA[With the release of .NET 10, C# 13 introduces a variety of quality-of-life improvements for developers. One of the standout features is the ability to define default parameter values in lambda expressions. This enhancement brings more flexibility and...]]></description><link>https://codewithrenato.com/default-parameters-in-lambda-expressions</link><guid isPermaLink="true">https://codewithrenato.com/default-parameters-in-lambda-expressions</guid><category><![CDATA[.NET]]></category><category><![CDATA[.NET 10]]></category><category><![CDATA[.NET 10 SDK]]></category><category><![CDATA[.NET 10 features]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Thu, 05 Jun 2025 03:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748962319421/5358bba5-9b6b-4352-a60f-35c2582154e6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With the release of .NET 10, C# 13 introduces a variety of quality-of-life improvements for developers. One of the standout features is the ability to define <strong>default parameter values in lambda expressions</strong>. This enhancement brings more flexibility and consistency to how we write inline functions.</p>
<h4 id="heading-what-changed">What Changed?</h4>
<p>Before C# 13, lambda expressions required all parameters to be explicitly supplied. If you wanted default values, you'd have to wrap your lambda in a method or use null checks and ternary operators inside the lambda body.</p>
<p>With C# 13, you can now define default values directly in the lambda parameter list, just like you can with regular methods.</p>
<h4 id="heading-example">Example</h4>
<p>Here’s how it looks in practice:</p>
<pre><code class="lang-csharp">Func&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>&gt; greet = (name = <span class="hljs-string">"World"</span>, prefix = <span class="hljs-string">"Hello"</span>) =&gt; <span class="hljs-string">$"<span class="hljs-subst">{prefix}</span>, <span class="hljs-subst">{name}</span>!"</span>;

Console.WriteLine(greet()); <span class="hljs-comment">// Hello, World!</span>
Console.WriteLine(greet(<span class="hljs-string">"Developer"</span>)); <span class="hljs-comment">// Hello, Developer!</span>
Console.WriteLine(greet(<span class="hljs-string">"Developer"</span>, <span class="hljs-string">"Hi"</span>)); <span class="hljs-comment">// Hi, Developer!</span>
</code></pre>
<p>This makes lambda expressions significantly more concise and expressive, especially when used in callbacks, event handlers, or functional programming patterns.</p>
<h4 id="heading-why-it-matters">Why It Matters</h4>
<p>Adding default parameters to lambdas helps in several ways:</p>
<ul>
<li><p><strong>Cleaner Code</strong>: Removes the need for wrapper methods or inline null checks.</p>
</li>
<li><p><strong>More Expressive</strong>: The intent is clearer when default behavior is defined right at the parameter level.</p>
</li>
<li><p><strong>Useful in Functional Scenarios</strong>: When passing lambdas into LINQ, configuration setups, or task pipelines, it reduces boilerplate.</p>
</li>
</ul>
<h4 id="heading-limitations">Limitations</h4>
<p>While this is a powerful feature, there are some caveats to be aware of:</p>
<ul>
<li><p>You must use explicitly-typed lambda expressions. Type inference alone won't work when using default values.</p>
</li>
<li><p>This feature does <strong>not</strong> apply to anonymous methods (<code>delegate { }</code>).</p>
</li>
<li><p>It may not be supported by older tooling or analyzers that haven't yet been updated for C# 13.</p>
</li>
</ul>
<h4 id="heading-summary">Summary</h4>
<p>The default parameter values in lambda expressions is a small but impactful improvement in C# 13. It aligns lambdas more closely with traditional methods and opens up new, cleaner ways to write inline logic.</p>
<p>As C# continues to evolve alongside .NET, these kinds of enhancements demonstrate a strong commitment to developer productivity and language consistency.</p>
<h4 id="heading-references">References</h4>
<ul>
<li><p><a target="_blank" href="https://github.com/dotnet/csharplang/issues/2306">C# Language Proposal - Default Parameter Values in Lambdas</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10">.NET 10 Preview Release Notes</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to trigger .NET Lambdas with S3 Events]]></title><description><![CDATA[When a file lands in S3, sometimes you want that to automatically trigger backend processing: image resizing, virus scanning, or sending a notification. AWS makes this possible using S3 event notifications to trigger Lambda functions. In this post, w...]]></description><link>https://codewithrenato.com/how-to-trigger-net-lambdas-with-s3-events</link><guid isPermaLink="true">https://codewithrenato.com/how-to-trigger-net-lambdas-with-s3-events</guid><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[S3]]></category><category><![CDATA[S3-bucket]]></category><category><![CDATA[#EventDrivenArchitecture]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Wed, 04 Jun 2025 06:00:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748961610868/b8370c7e-f490-4fcb-ad23-fc2c7887945a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When a file lands in S3, sometimes you want that to automatically trigger backend processing: image resizing, virus scanning, or sending a notification. AWS makes this possible using <strong>S3 event notifications</strong> to trigger <strong>Lambda functions</strong>. In this post, we’ll walk through wiring an S3 upload event to a .NET Lambda function.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ul>
<li><p><a target="_blank" href="https://dotnet.microsoft.com/download">.NET 6 or later SDK</a></p>
</li>
<li><p>AWS CLI (configured with credentials)</p>
</li>
<li><p>Amazon.Lambda.Tools CLI (<code>dotnet tool install -g Amazon.Lambda.Tools</code>)</p>
</li>
<li><p>An existing or new S3 bucket</p>
</li>
</ul>
<h3 id="heading-step-1-create-the-lambda-project">Step 1: Create the Lambda Project</h3>
<p>Start with the empty Lambda template:</p>
<pre><code class="lang-bash">dotnet new lambda.EmptyFunction --name S3TriggeredLambda
<span class="hljs-built_in">cd</span> S3TriggeredLambda
</code></pre>
<h3 id="heading-step-2-define-the-handler-for-s3-events">Step 2: Define the Handler for S3 Events</h3>
<p>Update <code>Function.cs</code> to accept an <code>S3Event</code> and log the key of the uploaded file:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Amazon.Lambda.S3Events;
<span class="hljs-keyword">using</span> Amazon.S3;
<span class="hljs-keyword">using</span> Amazon.S3.Util;
<span class="hljs-keyword">using</span> Amazon.Lambda.Core;

[<span class="hljs-meta">assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))</span>]

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Function</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IAmazonS3 _s3Client;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Function</span>(<span class="hljs-params"></span>) : <span class="hljs-title">this</span>(<span class="hljs-params">new AmazonS3Client(</span>))</span> { }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Function</span>(<span class="hljs-params">IAmazonS3 s3Client</span>)</span>
    {
        _s3Client = s3Client;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">FunctionHandler</span>(<span class="hljs-params">S3Event evnt, ILambdaContext context</span>)</span>
    {
        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> <span class="hljs-keyword">record</span> <span class="hljs-title">in</span> <span class="hljs-title">evnt.Records</span>)
        {
            <span class="hljs-keyword">var</span> bucket = record.S3.Bucket.Name;
            <span class="hljs-keyword">var</span> key = record.S3.Object.Key;

            context.Logger.LogLine($"File uploaded to S3: {bucket}/{key}");

            // Optional: read content
            <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> _s3Client.GetObjectAsync(bucket, key);
            <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> reader = <span class="hljs-keyword">new</span> StreamReader(response.ResponseStream);
            <span class="hljs-keyword">var</span> content = <span class="hljs-keyword">await</span> reader.ReadToEndAsync();

            context.Logger.LogLine(<span class="hljs-string">$"First 100 characters: <span class="hljs-subst">{content[..Math.Min(<span class="hljs-number">100</span>, content.Length)]}</span>"</span>);
        }
    }
}
</code></pre>
<h3 id="heading-step-3-configure-lambda-deployment">Step 3: Configure Lambda Deployment</h3>
<p>Create <code>aws-lambda-tools-defaults.json</code>:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"profile"</span>: <span class="hljs-string">"default"</span>,
  <span class="hljs-attr">"region"</span>: <span class="hljs-string">"us-east-1"</span>,
  <span class="hljs-attr">"configuration"</span>: <span class="hljs-string">"Release"</span>,
  <span class="hljs-attr">"framework"</span>: <span class="hljs-string">"net6.0"</span>,
  <span class="hljs-attr">"function-runtime"</span>: <span class="hljs-string">"dotnet6"</span>,
  <span class="hljs-attr">"function-handler"</span>: <span class="hljs-string">"S3TriggeredLambda::S3TriggeredLambda.Function::FunctionHandler"</span>,
  <span class="hljs-attr">"function-memory-size"</span>: <span class="hljs-number">256</span>,
  <span class="hljs-attr">"function-timeout"</span>: <span class="hljs-number">30</span>,
  <span class="hljs-attr">"function-name"</span>: <span class="hljs-string">"S3TriggeredLambda"</span>,
  <span class="hljs-attr">"function-role"</span>: <span class="hljs-string">"arn:aws:iam::123456789012:role/your-lambda-role"</span>
}
</code></pre>
<h3 id="heading-step-4-deploy-the-function">Step 4: Deploy the Function</h3>
<pre><code class="lang-bash">dotnet lambda deploy-function
</code></pre>
<p>Take note of the function name or ARN printed after deployment.</p>
<h3 id="heading-step-5-configure-s3-to-trigger-the-lambda">Step 5: Configure S3 to Trigger the Lambda</h3>
<p>Use the AWS CLI or Console to attach the Lambda as a notification target for your S3 bucket.</p>
<p><strong>Option A: Using AWS Console</strong></p>
<ul>
<li><p>Go to your S3 bucket</p>
</li>
<li><p>Choose <strong>Properties &gt; Event notifications</strong></p>
</li>
<li><p>Create a new event</p>
<ul>
<li><p>Event type: <strong>PUT</strong></p>
</li>
<li><p>Destination: <strong>Lambda function</strong></p>
</li>
<li><p>Select your Lambda</p>
</li>
</ul>
</li>
</ul>
<p><strong>Option B: Using AWS CLI</strong></p>
<pre><code class="lang-bash">aws s3api put-bucket-notification-configuration --bucket your-bucket-name --notification-configuration <span class="hljs-string">'{
  "LambdaFunctionConfigurations": [
    {
      "LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:S3TriggeredLambda",
      "Events": ["s3:ObjectCreated:*"]
    }
  ]
}'</span>
</code></pre>
<p>Make sure your Lambda function has permission to be invoked by S3:</p>
<pre><code class="lang-bash">aws lambda add-permission \
  --function-name S3TriggeredLambda \
  --principal s3.amazonaws.com \
  --statement-id s3invoke \
  --action <span class="hljs-string">"lambda:InvokeFunction"</span> \
  --source-arn arn:aws:s3:::your-bucket-name
</code></pre>
<h3 id="heading-step-6-test-the-integration">Step 6: Test the Integration</h3>
<p>Upload a file to your S3 bucket:</p>
<pre><code class="lang-bash">aws s3 cp sample.txt s3://your-bucket-name/
</code></pre>
<p>Then check the Lambda logs:</p>
<pre><code class="lang-bash">aws logs describe-log-groups
aws logs get-log-events --log-group-name /aws/lambda/S3TriggeredLambda --log-stream-name &lt;latest-stream-name&gt;
</code></pre>
<p>You should see log lines indicating that your Lambda processed the S3 object.</p>
<h3 id="heading-cleanup">Cleanup</h3>
<p>To avoid charges, delete the Lambda function and remove the S3 notification:</p>
<pre><code class="lang-bash">aws lambda delete-function --function-name S3TriggeredLambda
</code></pre>
<h3 id="heading-summary">Summary</h3>
<p>You’ve now connected S3 with a .NET Lambda to react to file uploads in real time. This pattern is ideal for file processing pipelines, ingestion workflows, or triggering downstream services without managing infrastructure.</p>
<h3 id="heading-references">References</h3>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/dotnet.html">AWS Lambda for .NET</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/NotificationHowTo.html">S3 Event Notification Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.nuget.org/packages/Amazon.Lambda.S3Events">Amazon.Lambda.S3Events Package</a> text</p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/with-s3-example.html">Lambda Permission Policy for S3</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/">Working with AWS SDK for .NET</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Deploying .NET APIs to AWS Lambda with API Gateway]]></title><description><![CDATA[Serverless doesn't mean giving up full-featured APIs. With AWS Lambda and API Gateway, you can run .NET Web APIs in a fully managed, scalable way without provisioning servers. In this post, we'll walk through deploying a minimal .NET API to AWS Lambd...]]></description><link>https://codewithrenato.com/deploying-net-apis-to-aws-lambda-with-api-gateway</link><guid isPermaLink="true">https://codewithrenato.com/deploying-net-apis-to-aws-lambda-with-api-gateway</guid><category><![CDATA[.NET]]></category><category><![CDATA[C#]]></category><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[aws-apigateway]]></category><category><![CDATA[AWS API Gateway]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Tue, 03 Jun 2025 03:00:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748960304091/d37d0005-6b38-42cc-a28a-2a8b4d605879.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Serverless doesn't mean giving up full-featured APIs. With AWS Lambda and API Gateway, you can run .NET Web APIs in a fully managed, scalable way without provisioning servers. In this post, we'll walk through deploying a minimal .NET API to AWS Lambda using API Gateway as the front door.</p>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p><a target="_blank" href="https://dotnet.microsoft.com/download">.NET 6 or later SDK</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html">AWS CLI</a> (configured with credentials)</p>
</li>
<li><p><a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">Amazon.Lambda.Tools CLI</a></p>
</li>
</ul>
<p>Install the AWS Lambda tools globally if you haven’t already:</p>
<pre><code class="lang-bash">dotnet tool install -g Amazon.Lambda.Tools
</code></pre>
<h2 id="heading-step-1-create-a-minimal-api">Step 1: Create a Minimal API</h2>
<p>We'll start with a simple .NET 6+ Web API using Minimal APIs:</p>
<pre><code class="lang-bash">dotnet new webapi -n MyLambdaApi
<span class="hljs-built_in">cd</span> MyLambdaApi
</code></pre>
<p>(Optional) Clean up the template a bit and define your own minimal endpoint in <code>Program.cs</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> app = WebApplication.Create(args);
app.MapGet(<span class="hljs-string">"/hello"</span>, () =&gt; <span class="hljs-string">"Hello from Lambda!"</span>);
app.Run();
</code></pre>
<h2 id="heading-step-2-add-lambda-support">Step 2: Add Lambda Support</h2>
<p>Install the necessary NuGet packages:</p>
<pre><code class="lang-bash">dotnet add package Amazon.Lambda.AspNetCoreServer
dotnet add package Amazon.Lambda.RuntimeSupport
</code></pre>
<p>Then, add a new class <code>LambdaEntryPoint.cs</code>:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">LambdaEntryPoint</span> : <span class="hljs-title">Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction</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">Init</span>(<span class="hljs-params">IWebHostBuilder builder</span>)</span>
    {
        builder.UseStartup&lt;Startup&gt;();
    }
}
</code></pre>
<p>Make sure <code>Startup.cs</code> exists and configures your services like normal.</p>
<hr />
<h2 id="heading-step-3-add-lambda-project-configuration">Step 3: Add Lambda Project Configuration</h2>
<p>Create a <code>aws-lambda-tools-defaults.json</code> file in the project root:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"profile"</span>: <span class="hljs-string">"default"</span>,
  <span class="hljs-attr">"region"</span>: <span class="hljs-string">"us-east-1"</span>,
  <span class="hljs-attr">"configuration"</span>: <span class="hljs-string">"Release"</span>,
  <span class="hljs-attr">"framework"</span>: <span class="hljs-string">"net6.0"</span>,
  <span class="hljs-attr">"function-runtime"</span>: <span class="hljs-string">"dotnet6"</span>,
  <span class="hljs-attr">"function-handler"</span>: <span class="hljs-string">"MyLambdaApi::MyLambdaApi.LambdaEntryPoint::FunctionHandlerAsync"</span>,
  <span class="hljs-attr">"function-memory-size"</span>: <span class="hljs-number">512</span>,
  <span class="hljs-attr">"function-timeout"</span>: <span class="hljs-number">30</span>,
  <span class="hljs-attr">"function-name"</span>: <span class="hljs-string">"MyLambdaApi"</span>,
  <span class="hljs-attr">"function-role"</span>: <span class="hljs-string">"arn:aws:iam::123456789012:role/your-lambda-role"</span>
}
</code></pre>
<hr />
<h2 id="heading-step-4-deploy-to-aws-lambda">Step 4: Deploy to AWS Lambda</h2>
<p>Build and deploy using the Lambda tools:</p>
<pre><code class="lang-bash">dotnet lambda deploy-serverless
</code></pre>
<p>This command packages your app, creates a Lambda function, and wires it to an API Gateway endpoint.</p>
<p>After deployment, it will print a URL like:</p>
<pre><code class="lang-bash">https://xyz123.execute-api.us-east-1.amazonaws.com/Prod/hello
</code></pre>
<p>You can test it using <code>curl</code> or a browser.</p>
<hr />
<h2 id="heading-step-5-test-the-endpoint">Step 5: Test the Endpoint</h2>
<pre><code class="lang-bash">curl https://xyz123.execute-api.us-east-1.amazonaws.com/Prod/hello
<span class="hljs-comment"># → Hello from Lambda!</span>
</code></pre>
<h2 id="heading-cleanup">Cleanup</h2>
<p>To avoid charges:</p>
<pre><code class="lang-bash">aws cloudformation delete-stack --stack-name MyLambdaApi
</code></pre>
<hr />
<h2 id="heading-summary">Summary</h2>
<p>You’ve just deployed a real .NET Web API to AWS Lambda using API Gateway with no servers in sight. This pattern is perfect for lightweight APIs, backend services, and microservices.</p>
<p>Stay tuned for the next post in the CloudSharp series, where we'll wire this API to a DynamoDB backend.</p>
<h3 id="heading-references">References</h3>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/dotnet.html">AWS Lambda for .NET</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">Amazon.Lambda.Tools GitHub Repo</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/csharp-deployment-package.html">Deploying .NET Lambda Functions</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis">ASP.NET Core Minimal APIs</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-getting-started-with-rest-apis.html">API Gateway and Lambda Integration</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Consuming DynamoDB Streams with .NET Lambda]]></title><description><![CDATA[DynamoDB Streams provide a powerful way to capture changes in your DynamoDB tables and react to them in real-time. In this post, we'll walk through how to consume these streams using a .NET AWS Lambda function.
Prerequisites
Before you begin, make su...]]></description><link>https://codewithrenato.com/consuming-dynamodb-streams-with-net-lambda</link><guid isPermaLink="true">https://codewithrenato.com/consuming-dynamodb-streams-with-net-lambda</guid><category><![CDATA[DynamoDB]]></category><category><![CDATA[AWS]]></category><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Mon, 02 Jun 2025 19:12:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749041137402/e25d36cb-33e8-4db8-bff1-ea32a01f49cc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>DynamoDB Streams provide a powerful way to capture changes in your DynamoDB tables and react to them in real-time. In this post, we'll walk through how to consume these streams using a .NET AWS Lambda function.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you begin, make sure you have the following:</p>
<ul>
<li><p><strong>.NET SDK (&gt;= 6.0)</strong> installed: <a target="_blank" href="https://dotnet.microsoft.com/download">https://dotnet.microsoft.com/download</a></p>
</li>
<li><p><strong>AWS CLI</strong> configured with credentials and default region: <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html">Installing the AWS CLI</a></p>
</li>
<li><p><strong>Amazon.Lambda.Templates</strong> installed:</p>
<pre><code class="lang-csharp">  dotnet <span class="hljs-keyword">new</span> install Amazon.Lambda.Templates
</code></pre>
</li>
<li><p><strong>An existing DynamoDB table</strong> or permissions to create one</p>
</li>
<li><p><strong>Basic familiarity with AWS Lambda and DynamoDB</strong></p>
</li>
<li><p>(Optional) <strong>AWS Toolkit for Visual Studio</strong> if you prefer a GUI deployment experience</p>
</li>
</ul>
<h2 id="heading-what-are-dynamodb-streams">What Are DynamoDB Streams?</h2>
<p>DynamoDB Streams capture table activity (inserts, updates, and deletes) and store the change records in a stream. You can attach an AWS Lambda function to the stream so it automatically gets invoked when changes occur.</p>
<p>Use cases include:</p>
<ul>
<li><p>Real-time analytics</p>
</li>
<li><p>Auditing and logging</p>
</li>
<li><p>Replicating data to other systems</p>
</li>
</ul>
<h2 id="heading-enabling-streams-on-a-table">Enabling Streams on a Table</h2>
<p>You can enable streams using the AWS Console, CLI, or CloudFormation. For example, using AWS CLI:</p>
<pre><code class="lang-bash">aws dynamodb update-table \
  --table-name MyTable \
  --stream-specification StreamEnabled=<span class="hljs-literal">true</span>,StreamViewType=NEW_AND_OLD_IMAGES
</code></pre>
<p>The <code>StreamViewType</code> determines what data is captured:</p>
<ul>
<li><p><code>KEYS_ONLY</code></p>
</li>
<li><p><code>NEW_IMAGE</code></p>
</li>
<li><p><code>OLD_IMAGE</code></p>
</li>
<li><p><code>NEW_AND_OLD_IMAGES</code> (recommended for most cases)</p>
</li>
</ul>
<h2 id="heading-creating-a-net-lambda-to-process-stream-records">Creating a .NET Lambda to Process Stream Records</h2>
<p>Let's build a .NET Lambda that listens to a DynamoDB Stream and processes the records.</p>
<h3 id="heading-1-create-a-new-lambda-project">1. Create a New Lambda Project</h3>
<p>Use the AWS Lambda template:</p>
<pre><code class="lang-bash">dotnet new lambda.DynamoDBEventFunction -n DynamoDbStreamConsumer
<span class="hljs-built_in">cd</span> DynamoDbStreamConsumer
</code></pre>
<h3 id="heading-2-update-the-function-handler">2. Update the Function Handler</h3>
<p>In <code>Function.cs</code>You’ll find a method like this:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">FunctionHandler</span>(<span class="hljs-params">DynamoDBEvent dynamoEvent, ILambdaContext context</span>)</span>
{
    <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> <span class="hljs-keyword">record</span> <span class="hljs-title">in</span> <span class="hljs-title">dynamoEvent.Records</span>)
    {
        context.Logger.LogInformation(<span class="hljs-string">$"Event ID: <span class="hljs-subst">{record.EventID}</span>"</span>);
        context.Logger.LogInformation(<span class="hljs-string">$"Event Name: <span class="hljs-subst">{record.EventName}</span>"</span>);

        <span class="hljs-keyword">if</span> (record.Dynamodb.NewImage != <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">var</span> item = Document.FromAttributeMap(record.Dynamodb.NewImage);
            context.Logger.LogInformation(<span class="hljs-string">$"New item: <span class="hljs-subst">{item.ToJsonPretty()}</span>"</span>);
        }

        <span class="hljs-keyword">if</span> (record.Dynamodb.OldImage != <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">var</span> oldItem = Document.FromAttributeMap(record.Dynamodb.OldImage);
            context.Logger.LogInformation(<span class="hljs-string">$"Old item: <span class="hljs-subst">{oldItem.ToJsonPretty()}</span>"</span>);
        }
    }
}
</code></pre>
<p>You can deserialize <code>NewImage</code> and <code>OldImage</code> to your model if needed.</p>
<h3 id="heading-3-deploy-the-lambda-function">3. Deploy the Lambda Function</h3>
<p>You can deploy the function using the AWS Toolkit for Visual Studio or with the CLI:</p>
<pre><code class="lang-bash">dotnet lambda deploy-function DynamoDbStreamConsumer
</code></pre>
<h3 id="heading-4-attach-the-stream-to-lambda">4. Attach the Stream to Lambda</h3>
<p>After deployment, link the DynamoDB stream to the Lambda:</p>
<pre><code class="lang-bash">aws lambda create-event-source-mapping \
  --function-name DynamoDbStreamConsumer \
  --event-source arn:aws:dynamodb:region:account-id:table/MyTable/stream/timestamp \
  --starting-position LATEST \
  --batch-size 10
</code></pre>
<p>This sets up the Lambda to process stream records in batches of 10.</p>
<h2 id="heading-error-handling-and-retries">Error Handling and Retries</h2>
<p>If your Lambda throws an exception, the batch is retried until it succeeds or expires. To avoid poison pills:</p>
<ul>
<li><p>Use a dead-letter queue (DLQ)</p>
</li>
<li><p>Enable <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-dynamodb-batchfailures">partial batch response</a> to skip bad records</p>
</li>
</ul>
<h2 id="heading-logging-and-monitoring">Logging and Monitoring</h2>
<p>Use Amazon CloudWatch Logs to debug and monitor stream processing. You can also add structured logging with Serilog or Microsoft.Extensions.Logging.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Using DynamoDB Streams with .NET Lambda functions allows you to build reactive, event-driven applications with minimal overhead. Whether you're tracking changes, replicating data, or triggering downstream processes, this integration is a powerful tool in your AWS toolkit.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html">DynamoDB Streams Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html">AWS Lambda with DynamoDB Streams</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/sdk-for-net/">AWS SDK for .NET</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">dotnet-lambda CLI Tool</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Running .NET on AWS Lambda: A Quick Start Guide]]></title><description><![CDATA[Amazon Web Services (AWS) Lambda is a serverless compute service that lets you run code without provisioning or managing servers. With support for .NET, you can build Lambda functions in C# and deploy them seamlessly. In this guide, you'll learn how ...]]></description><link>https://codewithrenato.com/running-net-on-aws-lambda-a-quick-start-guide</link><guid isPermaLink="true">https://codewithrenato.com/running-net-on-aws-lambda-a-quick-start-guide</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[C#]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Mon, 02 Jun 2025 11:09:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/gnxb59lGU1M/upload/000783d4dd9a88cc55d79de50ff43175.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Amazon Web Services (AWS) Lambda is a serverless compute service that lets you run code without provisioning or managing servers. With support for .NET, you can build Lambda functions in C# and deploy them seamlessly. In this guide, you'll learn how to create, build, and deploy a .NET Lambda function using the AWS .NET tooling.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start, make sure you have:</p>
<ul>
<li><p><a target="_blank" href="https://dotnet.microsoft.com/download">.NET SDK 6.0 or later</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">AWS CLI</a> (configured with credentials)</p>
</li>
<li><p><a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">Amazon.Lambda.Tools</a></p>
</li>
<li><p>An AWS account</p>
</li>
</ul>
<p>Install the Lambda tooling with:</p>
<pre><code class="lang-csharp">dotnet tool install -g Amazon.Lambda.Tools
</code></pre>
<h2 id="heading-step-1-create-a-lambda-project">Step 1: Create a Lambda Project</h2>
<p>You can create a new Lambda project using one of the AWS templates:</p>
<pre><code class="lang-bash">dotnet new lambda.EmptyFunction --name MyLambdaFunction
<span class="hljs-built_in">cd</span> MyLambdaFunction
</code></pre>
<p>The main function logic will be in <code>Function.cs</code>, under the method <code>FunctionHandler</code>.</p>
<h2 id="heading-step-2-write-your-function-logic">Step 2: Write Your Function Logic</h2>
<p>Edit <code>Function.cs</code> to match your logic. For example:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Function</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">FunctionHandler</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> input, ILambdaContext context</span>)</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-string">$"Hello, <span class="hljs-subst">{input}</span>!"</span>;
    }
}
</code></pre>
<h2 id="heading-step-3-test-locally">Step 3: Test Locally</h2>
<p>You can test the function locally with:</p>
<pre><code class="lang-bash">dotnet lambda invoke-function MyLambdaFunction --payload <span class="hljs-string">"World"</span>
</code></pre>
<hr />
<h2 id="heading-step-4-deploy-to-aws">Step 4: Deploy to AWS</h2>
<p>Deploy using the Lambda tools:</p>
<pre><code class="lang-bash">dotnet lambda deploy-function MyLambdaFunction
</code></pre>
<p>You’ll be prompted for details like the IAM role, region, and function name. You can also use <code>--function-role</code>, <code>--region</code>, and other flags to script deployments.</p>
<hr />
<h2 id="heading-step-5-invoke-from-aws-console-or-api">Step 5: Invoke from AWS Console or API</h2>
<p>Once deployed, you can:</p>
<ul>
<li><p>Test it from the <a target="_blank" href="https://console.aws.amazon.com/lambda/">AWS Lambda Console</a></p>
</li>
<li><p>Invoke it using the AWS CLI:</p>
</li>
</ul>
<pre><code class="lang-bash">aws lambda invoke \
  --function-name MyLambdaFunction \
  --payload <span class="hljs-string">"\"John Doe\""</span> \
  output.json
</code></pre>
<hr />
<h2 id="heading-optional-set-up-api-gateway">Optional: Set Up API Gateway</h2>
<p>To expose your Lambda via HTTP, connect it to API Gateway:</p>
<ul>
<li><p>In the AWS Console, go to API Gateway.</p>
</li>
<li><p>Create a new HTTP API.</p>
</li>
<li><p>Add an integration with your Lambda.</p>
</li>
<li><p>Deploy the API and use the URL to invoke your Lambda.</p>
</li>
</ul>
<hr />
<h2 id="heading-cleanup">Cleanup</h2>
<p>To delete the Lambda and avoid charges:</p>
<pre><code class="lang-bash">aws lambda delete-function --function-name MyLambdaFunction
</code></pre>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>Running .NET on AWS Lambda is a great way to take advantage of serverless architecture while using C#. With a few CLI commands, you can deploy scalable functions to the cloud without managing infrastructure.</p>
<p>Need advanced patterns, like dependency injection, configuration, or async I/O? AWS .NET Lambda projects support all of them. Let me know if you want a follow-up post on those topics.</p>
<h3 id="heading-references">References</h3>
<ul>
<li><p><strong>AWS Lambda for .NET</strong><br />  <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/dotnet.html">https://docs.aws.amazon.com/lambda/latest/dg/dotnet.html</a></p>
</li>
<li><p><strong>AWS Toolkit for .NET CLI</strong> (Amazon.Lambda.Tools)<br />  <a target="_blank" href="https://github.com/aws/aws-extensions-for-dotnet-cli">https://github.com/aws/aws-extensions-for-dotnet-cli</a></p>
</li>
<li><p><strong>.NET Templates for AWS Lambda</strong><br />  <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-cli.html">https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-cli.html</a></p>
</li>
<li><p><strong>Deploying .NET Lambda Functions</strong><br />  <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/csharp-deployment-package.html">https://docs.aws.amazon.com/lambda/latest/dg/csharp-deployment-package.html</a></p>
</li>
<li><p><strong>Testing AWS Lambda Functions Locally</strong><br />  <a target="_blank" href="https://docs.aws.amazon.com/lambda/latest/dg/images-test.html">https://docs.aws.amazon.com/lambda/latest/dg/images-test.html</a></p>
</li>
<li><p><strong>AWS Lambda + API Gateway Integration</strong><br />  <a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-getting-started-with-rest-apis.html">https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-getting-started-with-rest-apis.html</a></p>
</li>
<li><p><strong>.NET on AWS – Official AWS Page</strong><br />  <a target="_blank" href="https://aws.amazon.com/net/">https://aws.amazon.com/net/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Introducing File-Based C# Apps]]></title><description><![CDATA[Traditionally, executing C# code required setting up a project structure with a .csproj file. With this new feature, developers can run standalone .cs files directly, akin to scripting languages like Python or JavaScript. This approach lowers the ent...]]></description><link>https://codewithrenato.com/introducing-file-based-c-apps</link><guid isPermaLink="true">https://codewithrenato.com/introducing-file-based-c-apps</guid><category><![CDATA[.NET]]></category><category><![CDATA[.NET 10]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Mon, 02 Jun 2025 10:39:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748860728371/f70a7888-d676-4c30-a606-2d3eebdd0fa0.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Traditionally, executing C# code required setting up a project structure with a <code>.csproj</code> file. With this new feature, developers can run standalone <code>.cs</code> files directly, akin to scripting languages like Python or JavaScript. This approach lowers the entry barrier for newcomers and accelerates the development workflow for seasoned programmers.</p>
<p><strong>Key Advantages:</strong></p>
<ul>
<li><p><strong>Immediate Execution</strong>: Run C# code instantly without project scaffolding.</p>
</li>
<li><p><strong>Simplified Tooling</strong>: No additional tools or dependencies required—just the <code>dotnet</code> CLI and your <code>.cs</code> file.</p>
</li>
<li><p><strong>Scalable Development</strong>: Easily transition from a single script to a full-fledged project as your application grows.</p>
</li>
</ul>
<h2 id="heading-enhancing-scripts-with-file-level-directives">Enhancing Scripts with File-Level Directives</h2>
<p>.NET 10 introduces file-level directives that bring additional capabilities to single-file C# applications:</p>
<ul>
<li><p><strong>NuGet Package References</strong>: Include external packages directly within your script using the <code>#:package</code> directive.</p>
<pre><code class="lang-csharp">  <span class="hljs-meta">#:package Humanizer@2.14.1</span>
  <span class="hljs-keyword">using</span> Humanizer;

  <span class="hljs-keyword">var</span> releaseDate = DateTimeOffset.Parse(<span class="hljs-string">"2024-12-03"</span>);
  <span class="hljs-keyword">var</span> duration = DateTimeOffset.Now - releaseDate;
  Console.WriteLine(<span class="hljs-string">$"Released <span class="hljs-subst">{duration.Humanize()}</span> ago."</span>);
</code></pre>
</li>
<li><p><strong>SDK Specification</strong>: Define the SDK context (e.g., for web applications) with the <code>#:sdk</code></p>
<pre><code class="lang-csharp">  <span class="hljs-meta">#:sdk Microsoft.NET.Sdk.Web</span>
</code></pre>
</li>
<li><p><strong>MSBuild Properties</strong>: Set build properties like language version using the <code>#:property</code> directive.</p>
<pre><code class="lang-csharp">  <span class="hljs-meta">#:property LangVersion preview</span>
</code></pre>
</li>
<li><p><strong>Shebang Support</strong>: Create executable scripts on Unix-like systems with shebang lines.</p>
<pre><code class="lang-csharp">  <span class="hljs-meta">#!/usr/bin/dotnet run</span>
  Console.WriteLine(<span class="hljs-string">"Hello from a C# script!"</span>);
</code></pre>
</li>
</ul>
<p>Make the script executable and run it:</p>
<pre><code class="lang-bash">chmod +x app.cs
./app.cs
</code></pre>
<h2 id="heading-seamless-transition-to-project-based-applications">Seamless Transition to Project-Based Applications</h2>
<p>When your script evolves into a more complex application, you can convert it into a standard project effortlessly:</p>
<pre><code class="lang-bash">dotnet project convert app.cs
</code></pre>
<h2 id="heading-getting-started">Getting Started</h2>
<ol>
<li><p><strong>Install .NET 10 Preview 4</strong>: Download and install from the official <a target="_blank" href="https://dotnet.microsoft.com">.NET website</a>.</p>
</li>
<li><p><strong>Set Up Your Editor</strong>: For Visual Studio Code, install the C# Dev Kit and switch to the pre-release version of the C# extension to enable file-based app support.</p>
</li>
<li><p><strong>Write Your Script</strong>: Create a file named <code>hello.cs</code> with the following content:</p>
<pre><code class="lang-csharp"> Console.WriteLine(<span class="hljs-string">"Hello, world!"</span>);
</code></pre>
</li>
</ol>
<p><strong>Run Your Script</strong>: Execute the script using the command:</p>
<pre><code class="lang-bash">dotnet run hello.cs
</code></pre>
<h2 id="heading-embracing-a-more-accessible-c">Embracing a More Accessible C</h2>
<p>The introduction of <code>dotnet run app.cs</code> marks a significant step towards making C# more approachable and versatile. By streamlining the execution of C# code, Microsoft empowers developers to experiment, learn, and build applications more easily and efficiently.</p>
<h3 id="heading-referrences">Referrences</h3>
<ul>
<li><a target="_blank" href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/">https://devblogs.microsoft.com/dotnet/announcing-dotnet-run-app/</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Return Binary Content with AWS API Gateway and Lambda]]></title><description><![CDATA[If you've ever tried returning an image or a PDF from a Lambda function through API Gateway, you've probably hit a wall. Maybe the browser downloads a corrupt file, or your API returns a string of unreadable characters. I’ve been there, and the fix w...]]></description><link>https://codewithrenato.com/how-to-return-binary-content-with-aws-api-gateway-and-lambda</link><guid isPermaLink="true">https://codewithrenato.com/how-to-return-binary-content-with-aws-api-gateway-and-lambda</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws lambda]]></category><category><![CDATA[Python]]></category><category><![CDATA[Google API]]></category><dc:creator><![CDATA[Renato Ramos Nascimento]]></dc:creator><pubDate>Sun, 01 Jun 2025 15:20:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748791746618/d44e5c6c-9fc9-409d-8567-2bd1f267414c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you've ever tried returning an image or a PDF from a Lambda function through API Gateway, you've probably hit a wall. Maybe the browser downloads a corrupt file, or your API returns a string of unreadable characters. I’ve been there, and the fix wasn’t immediately obvious.</p>
<p>In this post, I’ll walk you through the complete setup for returning binary content (like images) from API Gateway using a Lambda function. I’ll also show how to configure it using SAM/CloudFormation and share a few lessons I wish I knew when I started.</p>
<h2 id="heading-why-is-binary-content-so-tricky-with-api-gateway">Why Is Binary Content So Tricky with API Gateway?</h2>
<p>API Gateway was originally built around text-based communication, like JSON or plain text. By default, it doesn’t expect raw binary. So when you return binary data, you must explicitly tell API Gateway how to handle it. If you skip any step, you’ll either get unreadable output or empty files.</p>
<h2 id="heading-stack-used">Stack Used</h2>
<ul>
<li><p>AWS Lambda (Python)</p>
</li>
<li><p>API Gateway (REST, not HTTP API)</p>
</li>
<li><p>AWS SAM (Serverless Application Model)</p>
</li>
<li><p>Google Maps API to fetch image content</p>
</li>
</ul>
<h2 id="heading-step-1-configure-binary-support-in-api-gateway">Step 1: Configure Binary Support in API Gateway</h2>
<p>You need to declare which binary media types your API supports. This is done in the <code>BinaryMediaTypes</code> section of the <code>AWS::ApiGateway::RestApi</code> resource.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">AWSTemplateFormatVersion:</span> <span class="hljs-string">'2010-09-09'</span>
<span class="hljs-attr">Transform:</span> <span class="hljs-string">AWS::Serverless-2016-10-31</span>
<span class="hljs-attr">Description:</span> <span class="hljs-string">Example</span> <span class="hljs-string">SAM</span> <span class="hljs-string">template</span> <span class="hljs-string">for</span> <span class="hljs-string">returning</span> <span class="hljs-string">binary</span> <span class="hljs-string">content</span> <span class="hljs-string">through</span> <span class="hljs-string">API</span> <span class="hljs-string">Gateway</span>

<span class="hljs-attr">Globals:</span>
  <span class="hljs-attr">Function:</span>
    <span class="hljs-attr">Timeout:</span> <span class="hljs-number">30</span>
    <span class="hljs-attr">Runtime:</span> <span class="hljs-string">python3.12</span>

<span class="hljs-attr">Resources:</span>
  <span class="hljs-attr">BinaryEnabledApi:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGateway::RestApi</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Name:</span> <span class="hljs-string">binary-enabled-api</span>
      <span class="hljs-attr">BinaryMediaTypes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"*/*"</span>
      <span class="hljs-attr">EndpointConfiguration:</span>
        <span class="hljs-attr">Types:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">PRIVATE</span>

  <span class="hljs-attr">ApiRootResource:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGateway::Resource</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">RestApiId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">BinaryEnabledApi</span>
      <span class="hljs-attr">ParentId:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">BinaryEnabledApi.RootResourceId</span>
      <span class="hljs-attr">PathPart:</span> <span class="hljs-string">"api"</span>

  <span class="hljs-attr">ImageResource:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGateway::Resource</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">RestApiId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">BinaryEnabledApi</span>
      <span class="hljs-attr">ParentId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">ApiRootResource</span>
      <span class="hljs-attr">PathPart:</span> <span class="hljs-string">"image"</span>

  <span class="hljs-attr">GetImageMethod:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGateway::Method</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">RestApiId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">BinaryEnabledApi</span>
      <span class="hljs-attr">ResourceId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">ImageResource</span>
      <span class="hljs-attr">HttpMethod:</span> <span class="hljs-string">GET</span>
      <span class="hljs-attr">AuthorizationType:</span> <span class="hljs-string">NONE</span>
      <span class="hljs-attr">Integration:</span>
        <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS_PROXY</span>
        <span class="hljs-attr">IntegrationHttpMethod:</span> <span class="hljs-string">POST</span>
        <span class="hljs-attr">Uri:</span> <span class="hljs-type">!Sub</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">"arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn}/invocations"</span>
          <span class="hljs-bullet">-</span> { <span class="hljs-attr">LambdaArn:</span> <span class="hljs-type">!GetAtt</span> <span class="hljs-string">BinaryLambdaFunction.Arn</span> }
        <span class="hljs-attr">PassthroughBehavior:</span> <span class="hljs-string">WHEN_NO_MATCH</span>

  <span class="hljs-attr">BinaryLambdaFunction:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::Serverless::Function</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">FunctionName:</span> <span class="hljs-string">binary-content-handler</span>
      <span class="hljs-attr">CodeUri:</span> <span class="hljs-string">src/</span>
      <span class="hljs-attr">Handler:</span> <span class="hljs-string">app.lambda_handler</span>
      <span class="hljs-attr">MemorySize:</span> <span class="hljs-number">128</span>
      <span class="hljs-attr">Tracing:</span> <span class="hljs-string">Active</span>
      <span class="hljs-attr">Policies:</span> <span class="hljs-string">AWSLambdaBasicExecutionRole</span>

  <span class="hljs-attr">LambdaInvokePermission:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::Lambda::Permission</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">Action:</span> <span class="hljs-string">lambda:InvokeFunction</span>
      <span class="hljs-attr">FunctionName:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">BinaryLambdaFunction</span>
      <span class="hljs-attr">Principal:</span> <span class="hljs-string">apigateway.amazonaws.com</span>
      <span class="hljs-attr">SourceArn:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${BinaryEnabledApi}/*/GET/api/image"</span>

  <span class="hljs-attr">ApiDeployment:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGateway::Deployment</span>
    <span class="hljs-attr">DependsOn:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">GetImageMethod</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">RestApiId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">BinaryEnabledApi</span>

  <span class="hljs-attr">ApiStage:</span>
    <span class="hljs-attr">Type:</span> <span class="hljs-string">AWS::ApiGateway::Stage</span>
    <span class="hljs-attr">Properties:</span>
      <span class="hljs-attr">StageName:</span> <span class="hljs-string">prod</span>
      <span class="hljs-attr">RestApiId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">BinaryEnabledApi</span>
      <span class="hljs-attr">DeploymentId:</span> <span class="hljs-type">!Ref</span> <span class="hljs-string">ApiDeployment</span>
      <span class="hljs-attr">MethodSettings:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">ResourcePath:</span> <span class="hljs-string">"/*"</span>
          <span class="hljs-attr">HttpMethod:</span> <span class="hljs-string">"*"</span>
          <span class="hljs-attr">MetricsEnabled:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">LoggingLevel:</span> <span class="hljs-string">INFO</span>

<span class="hljs-attr">Outputs:</span>
  <span class="hljs-attr">ApiInvokeUrl:</span>
    <span class="hljs-attr">Description:</span> <span class="hljs-string">Invoke</span> <span class="hljs-string">URL</span> <span class="hljs-string">for</span> <span class="hljs-string">the</span> <span class="hljs-string">binary-enabled</span> <span class="hljs-string">API</span>
    <span class="hljs-attr">Value:</span> <span class="hljs-type">!Sub</span> <span class="hljs-string">"https://${BinaryEnabledApi}.execute-api.${AWS::Region}.amazonaws.com/prod/api/image"</span>
</code></pre>
<p><strong>Important:</strong> Any time you update <code>BinaryMediaTypes</code>, make sure to redeploy your API and its stage. Changes won't take effect until that happens.</p>
<h2 id="heading-step-2-make-lambda-return-the-right-format">Step 2: Make Lambda Return the Right Format</h2>
<p>Your Lambda function must return a Base64-encoded string with specific fields set in the response.</p>
<h3 id="heading-example-returning-a-static-map-image">Example: Returning a Static Map Image</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> base64
<span class="hljs-keyword">import</span> requests

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lambda_handler</span>(<span class="hljs-params">event, context</span>):</span>
    image_url = <span class="hljs-string">"https://maps.googleapis.com/maps/api/staticmap?center=New+York&amp;zoom=13&amp;size=600x300&amp;key=YOUR_API_KEY"</span>
    response = requests.get(image_url)

    <span class="hljs-keyword">return</span> {
        <span class="hljs-string">"statusCode"</span>: <span class="hljs-number">200</span>,
        <span class="hljs-string">"headers"</span>: {
            <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"image/png"</span>
        },
        <span class="hljs-string">"body"</span>: base64.b64encode(response.content).decode(<span class="hljs-string">"utf-8"</span>),
        <span class="hljs-string">"isBase64Encoded"</span>: <span class="hljs-literal">True</span>
    }
</code></pre>
<h2 id="heading-step-3-test-with-real-clients">Step 3: Test with Real Clients</h2>
<h3 id="heading-tools-that-wont-work-reliably">Tools that won't work reliably:</h3>
<ul>
<li><p>AWS Console’s “Test” feature</p>
</li>
<li><p>CloudWatch Logs (shows only the base64 string)</p>
</li>
</ul>
<h3 id="heading-tools-that-work-correctly">Tools that work correctly:</h3>
<ul>
<li><p>Postman</p>
</li>
<li><p>Curl</p>
</li>
<li><p>Browsers</p>
</li>
</ul>
<h3 id="heading-example-curl-command">Example curl command:</h3>
<pre><code class="lang-bash">curl -H <span class="hljs-string">"Authorization: ..."</span> \
     https://your-api-id.execute-api.us-west-2.amazonaws.com/prod/api/image \
     --output result.png
</code></pre>
<h2 id="heading-common-pitfalls">Common Pitfalls</h2>
<p>Here are the mistakes that cost me the most time:</p>
<ul>
<li><p>Forgetting to set <code>"isBase64Encoded": true</code> in the Lambda response</p>
</li>
<li><p>Not setting the correct <code>Content-Type</code> header</p>
</li>
<li><p>Expecting binary output to work with the default test tools</p>
</li>
<li><p>Using HTTP API instead of REST API (binary support is limited in HTTP APIs)</p>
</li>
<li><p>Forgetting to redeploy after changing <code>BinaryMediaTypes</code></p>
</li>
</ul>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Returning binary content through API Gateway is not intuitive at first, but once you know what to configure, it works reliably. This setup is especially useful for services that return images, PDFs, or any kind of binary payload directly from Lambda.</p>
<p>If this helped you get your API working, feel free to share it or reach out. I’d love to hear how you used it in your project.</p>
<h3 id="heading-references">References</h3>
<ul>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html">API Gateway Binary Support Docs</a></p>
</li>
<li><p><a target="_blank" href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html">AWS SAM Template Reference</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>