<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dotnet | The AWS Blog</title><link>https://theawsblog.com/tags/dotnet/</link><description>Articles, tutorials and insights from the AWS community.</description><generator>Hugo</generator><language>en</language><managingEditor>@theawsblog (The AWS Blog)</managingEditor><webMaster>@theawsblog</webMaster><lastBuildDate>Fri, 07 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://theawsblog.com/tags/dotnet/index.xml" rel="self" type="application/rss+xml"/><item><title>Beyond the SDK Demo: Building Production-Ready .NET Agents with AgentCore</title><link>https://theawsblog.com/news/emiliano-montesdeoca/dotnet-ai-agents-bedrock-agentcore/</link><pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate><author>Emiliano Montesdeoca</author><guid>https://theawsblog.com/news/emiliano-montesdeoca/dotnet-ai-agents-bedrock-agentcore/</guid><description>Moving a .NET AI agent from an SDK sample to Amazon Bedrock AgentCore means making deliberate choices about streaming, sessions, testing, deployment, and portability.</description><content:encoded>&lt;p&gt;The first .NET AI agent is usually easy to demonstrate. Register a model client, add a handler, send a prompt, print the answer. The production version is where the interesting decisions begin: how do sessions stay separate, how do responses stream, how do you test without AWS credentials, and what exactly happens when the runtime scales or restarts?&lt;/p&gt;
&lt;p&gt;The AWS Developer Tools Blog&amp;rsquo;s &lt;a href="https://aws.amazon.com/blogs/developer/building-and-deploying-net-ai-agents-with-amazon-bedrock-agentcore/"&gt;AgentCore guide for .NET&lt;/a&gt; introduces &lt;code&gt;AWS.AgentCore.Hosting&lt;/code&gt; and the surrounding tooling for those concerns. The library &amp;ldquo;handles the operational concerns: scaling, session routing, health checking, and providing managed capabilities like conversation memory.&amp;rdquo; That is useful, but a hosting library cannot choose the boundaries of your application for you.&lt;/p&gt;
&lt;h2 id="choose-an-api-shape-that-leaves-room"&gt;Choose an API shape that leaves room&lt;/h2&gt;
&lt;p&gt;The package supports a source-generator experience and an extension-method experience in ASP.NET Core. The choice looks like syntax. It is really a choice about how much control the application will need around the handler.&lt;/p&gt;
&lt;p&gt;If the agent is a small, stable endpoint, generated wiring can keep the code compact. If the agent will need custom middleware, request correlation, policy checks, metrics, or dependency injection behavior, the extension method gives the team more room to shape the host. The source article describes middleware that can intercept every invocation. That is exactly where I would put cross-cutting concerns rather than scattering them through prompt handlers.&lt;/p&gt;
&lt;p&gt;Keep the handler thin either way. Put business operations, model selection, and external calls behind services that can be tested without the AgentCore runtime. Then the hosting layer remains an integration boundary, not the place where the entire application lives.&lt;/p&gt;
&lt;h2 id="streaming-changes-the-contract"&gt;Streaming changes the contract&lt;/h2&gt;
&lt;p&gt;Returning a complete string is comfortable, but it makes users wait for the slowest part of the generation. AgentCore supports returning &lt;code&gt;IAsyncEnumerable&amp;lt;string&amp;gt;&lt;/code&gt; so tokens can be streamed as they are produced. For conversational applications, the difference between first token latency and total response latency is visible immediately.&lt;/p&gt;
&lt;p&gt;Streaming also changes failure handling. A model call, memory lookup, or downstream tool can fail after the response has started. The caller cannot receive a new HTTP status code at that point, so the protocol needs a clear way to represent an error in the stream. Cancellation matters as well: when the browser disconnects, the handler should stop generating and release work instead of continuing to pay for a response nobody will read.&lt;/p&gt;
&lt;p&gt;Test partial output, cancellation, slow clients, and a failure after the first token. A streaming demo proves that bytes arrive. A production test proves that the system behaves when the stream is interrupted.&lt;/p&gt;
&lt;h2 id="session-ids-are-security-boundaries"&gt;Session IDs are security boundaries&lt;/h2&gt;
&lt;p&gt;AgentCore Memory combines a configured Memory ID with a session ID to load and store conversation history. That makes the session ID part of your security model, not just a convenience parameter.&lt;/p&gt;
&lt;p&gt;Two users must never share a session by accident. A missing session ID should fail clearly or create a deliberately scoped new session; it should not quietly fall back to a shared default. Add tests for two independent sessions, repeated calls in the same session, missing identifiers, and attempts to reuse another user&amp;rsquo;s identifier.&lt;/p&gt;
&lt;p&gt;The local emulator and &lt;code&gt;AWS.AgentCore.Testing&lt;/code&gt; package are valuable here. Use them to verify session behavior in CI without deploying the agent or requiring live AWS credentials. Memory integration is exactly the kind of feature that appears correct in a happy-path demo and fails in a multi-user system.&lt;/p&gt;
&lt;h2 id="deployment-is-still-architecture"&gt;Deployment is still architecture&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;dotnet aws deploy&lt;/code&gt; workflow makes deployment approachable, but it also hides choices. Container architecture, memory, VPC access, request header allowlists, and private service connectivity all affect runtime behavior.&lt;/p&gt;
&lt;p&gt;Read the generated resources and make those choices explicit. If the agent needs a private database or internal API, test the VPC path from the deployed runtime rather than assuming the local Aspire application proves it. If the service has a meaningful cold-start budget, measure it on the actual architecture you plan to run.&lt;/p&gt;
&lt;p&gt;Native AOT can reduce startup time, but it changes the development experience. Source-generated serialization and explicit service resolution may be required where flexible reflection-based binding previously worked. Decide whether cold-start reduction is worth that constraint for this workload instead of enabling AOT because it is available.&lt;/p&gt;
&lt;h2 id="keep-portability-in-the-business-layer"&gt;Keep portability in the business layer&lt;/h2&gt;
&lt;p&gt;AgentCore is a managed runtime, but the agent&amp;rsquo;s business logic can remain portable. Treat the handler as an adapter around services that know how to perform domain operations. Avoid spreading AgentCore-specific session and transport calls through every class.&lt;/p&gt;
&lt;p&gt;That design gives you a useful fallback: the same domain service can be hosted in another ASP.NET Core application, tested in isolation, or moved to another container platform if the operational requirement changes. Portability is not free, but it is much cheaper when the boundary is intentional from the beginning.&lt;/p&gt;
&lt;h2 id="my-pre-production-checklist"&gt;My pre-production checklist&lt;/h2&gt;
&lt;p&gt;Before the first real users arrive, I would require:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;separate-session tests and a clear session ID ownership model,&lt;/li&gt;
&lt;li&gt;streaming tests covering cancellation and mid-response failures,&lt;/li&gt;
&lt;li&gt;emulator-backed integration tests in CI,&lt;/li&gt;
&lt;li&gt;health and correlation telemetry around every invocation,&lt;/li&gt;
&lt;li&gt;a documented deployment and rollback path,&lt;/li&gt;
&lt;li&gt;a decision on JIT versus Native AOT based on measured startup data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The AgentCore hosting library removes a lot of plumbing. That is its value. The remaining work is the architecture around the plumbing: state, failure, identity, and operations. The SDK demo gets you to the first response. Those decisions are what get the agent to production.&lt;/p&gt;</content:encoded></item><item><title>Cross-Account ECS Telemetry Needs a Platform Boundary</title><link>https://theawsblog.com/news/emiliano-montesdeoca/ecs-cross-account-telemetry-adot-gateway/</link><pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate><author>Emiliano Montesdeoca</author><guid>https://theawsblog.com/news/emiliano-montesdeoca/ecs-cross-account-telemetry-adot-gateway/</guid><description>A centralized AWS Distro for OpenTelemetry gateway can reduce per-task overhead and close observability gaps across multi-account ECS environments, including Windows .NET workloads.</description><content:encoded>&lt;p&gt;A collector sidecar is a friendly pattern right up until an organization has hundreds of ECS tasks. Then the same convenience becomes a platform tax: every task carries collector CPU and memory, every account carries configuration, and every update becomes another rollout.&lt;/p&gt;
&lt;p&gt;The problem is even sharper for Windows .NET Framework workloads. A Linux collector sidecar is not a universal answer for an IIS application running on Windows. You either create a separate collection strategy or accept a hole in the telemetry map.&lt;/p&gt;
&lt;p&gt;The AWS Containers Blog&amp;rsquo;s &lt;a href="https://aws.amazon.com/blogs/containers/centralize-cross-account-amazon-ecs-telemetry-with-an-adot-gateway/"&gt;cross-account ECS telemetry pattern&lt;/a&gt; moves collection into a dedicated observability account. Workloads send OpenTelemetry Protocol data over private connectivity to a centralized AWS Distro for OpenTelemetry gateway, which exports traces to AWS X-Ray and metrics and logs to CloudWatch.&lt;/p&gt;
&lt;p&gt;That is more than a collector deployment. It is an observability platform boundary.&lt;/p&gt;
&lt;h2 id="sidecar-versus-gateway"&gt;Sidecar versus gateway&lt;/h2&gt;
&lt;p&gt;The sidecar model has a real advantage: the collector sits next to the application and is easy to understand. For a small service, that local ownership may be exactly right. The cost appears when the pattern is copied everywhere.&lt;/p&gt;
&lt;p&gt;A collector per task multiplies baseline resource usage. It also multiplies configuration drift. A change to batching, sampling, authentication, or metric dimensions must reach every task definition and every account. Windows workloads introduce another constraint because the application may not be able to host the same Linux sidecar at all.&lt;/p&gt;
&lt;p&gt;A centralized gateway reverses those trade-offs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one fleet to patch and configure,&lt;/li&gt;
&lt;li&gt;one endpoint for workloads across accounts,&lt;/li&gt;
&lt;li&gt;one place to control batching and export behavior,&lt;/li&gt;
&lt;li&gt;a shared path for Linux and Windows applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The price is network and platform complexity. You need private routing between workload VPCs and the observability VPC, capacity planning for the gateway, and a clear ownership model for telemetry arriving from many accounts.&lt;/p&gt;
&lt;h2 id="windows-net-is-the-forcing-function"&gt;Windows .NET is the forcing function&lt;/h2&gt;
&lt;p&gt;For a Windows .NET Framework application hosted in IIS, the gateway is not just a cost optimization. It can be the practical way to reach the destination telemetry systems without running a Linux collector beside the application.&lt;/p&gt;
&lt;p&gt;The source pattern configures OpenTelemetry instrumentation in the Windows image and points the OTLP exporter at the gateway&amp;rsquo;s internal network load balancer. IIS worker processes need the right machine-level environment variables and instrumentation registration. That detail is easy to miss because a Linux container tutorial can make environment configuration look universal.&lt;/p&gt;
&lt;p&gt;The useful design principle is to keep application instrumentation consistent while moving platform-specific collection into the shared gateway. The application emits standard OTLP. The gateway owns export, batching, dimensions, and destination-specific details.&lt;/p&gt;
&lt;h2 id="the-failure-mode-that-wastes-a-day"&gt;The failure mode that wastes a day&lt;/h2&gt;
&lt;p&gt;Centralization does not make networking disappear. One common failure is a target health-check loop: ECS tasks send telemetry to the load balancer, but the collector targets never become healthy. The security group allows client CIDRs but blocks health checks originating from the load balancer subnets.&lt;/p&gt;
&lt;p&gt;Allow the health-check path explicitly. Test the gateway&amp;rsquo;s health endpoint from the load balancer perspective, then test OTLP reachability from a workload VPC. A collector can be running and still be unreachable, which makes task logs unnecessarily confusing.&lt;/p&gt;
&lt;p&gt;Preserve source identity as well. The gateway should add fallback metadata only when the workload did not provide it. If the collector overwrites account, cluster, or service attributes, all telemetry starts looking as if it came from the observability account. Centralization is useful only when the origin remains trustworthy.&lt;/p&gt;
&lt;h2 id="choose-the-layer-that-is-missing"&gt;Choose the layer that is missing&lt;/h2&gt;
&lt;p&gt;The AWS article distinguishes collection from visualization. CloudWatch cross-account observability can give operators a shared view of telemetry that is already being collected. It does not solve an instrumentation or ingestion gap. An ADOT gateway operates earlier, at the collection boundary.&lt;/p&gt;
&lt;p&gt;That distinction keeps teams from deploying a gateway to solve the wrong problem. If every workload already emits reliable telemetry and the pain is searching across accounts, use the native cross-account viewing features. If applications cannot collect consistently, or you need one governed exporter configuration, the gateway is the better fit.&lt;/p&gt;
&lt;h2 id="a-practical-rollout"&gt;A practical rollout&lt;/h2&gt;
&lt;p&gt;I would start with one observability account, two Availability Zones, and one representative Linux service plus one Windows .NET service. Before onboarding more accounts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Establish Transit Gateway or equivalent private routing and verify non-overlapping CIDR ranges.&lt;/li&gt;
&lt;li&gt;Run the gateway behind an internal load balancer and alarm on unhealthy targets.&lt;/li&gt;
&lt;li&gt;Send a test trace, metric, and log from each operating-system family.&lt;/li&gt;
&lt;li&gt;Check that account, cluster, service, and environment dimensions survive the gateway.&lt;/li&gt;
&lt;li&gt;Limit CloudWatch metric dimensions to fields you actually query; high-cardinality metadata becomes an ingestion bill quickly.&lt;/li&gt;
&lt;li&gt;Add a second collector task and exercise a replacement while telemetry is flowing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The source article estimates that a small centralized gateway can run in the low tens of dollars per month before telemetry ingestion costs. The exact number will vary, but the broader lesson is stable: compare one governed fleet with the cumulative cost of per-task collectors, not with a zero-cost imaginary sidecar.&lt;/p&gt;
&lt;p&gt;A centralized ADOT gateway is not automatically the right answer for every ECS estate. It is a strong answer when multi-account scale, Windows .NET support, and consistent collection are the actual constraints. Treat it as platform infrastructure, preserve source identity, and test the failure path before calling observability complete.&lt;/p&gt;</content:encoded></item><item><title>Response Streaming for .NET on Lambda Changes Perceived Latency</title><link>https://theawsblog.com/news/emiliano-montesdeoca/dotnet-lambda-response-streaming/</link><pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate><author>Emiliano Montesdeoca</author><guid>https://theawsblog.com/news/emiliano-montesdeoca/dotnet-lambda-response-streaming/</guid><description>Response streaming for .NET on AWS Lambda can deliver tokens and large responses earlier, but buffering, headers, timeouts, and disconnects become part of the API contract.</description><content:encoded>&lt;p&gt;A response that takes five seconds can feel faster than a response that takes three seconds if the first one starts showing useful content immediately. That is the practical reason response streaming matters for .NET workloads on AWS Lambda.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://aws.amazon.com/blogs/developer/announcing-response-streaming-for-net-on-aws-lambda/"&gt;AWS announcement&lt;/a&gt; adds response streaming support for .NET Lambda functions. Instead of buffering the complete result and returning it once the handler finishes, the function can send bytes as they become available. That is especially useful for token generation, large exports, and responses that do not fit comfortably in the traditional buffer.&lt;/p&gt;
&lt;p&gt;The feature is valuable. It also changes the failure model of the endpoint.&lt;/p&gt;
&lt;h2 id="buffering-hides-time-from-the-user"&gt;Buffering hides time from the user&lt;/h2&gt;
&lt;p&gt;A model can generate a token at a time, but a buffered Lambda response makes the client wait for all of them. The same problem appears with a large CSV export: the function spends time building the response, and the caller sees nothing until serialization and buffering are complete.&lt;/p&gt;
&lt;p&gt;Streaming changes that first-byte experience. With a &lt;code&gt;StreamWriter&lt;/code&gt;, the handler writes a chunk and flushes it to the caller. For an LLM, that means the interface can render the first tokens while the model continues working.&lt;/p&gt;
&lt;p&gt;The flush is important. A streaming API that never flushes is just a buffered API with extra ceremony. The source examples use explicit &lt;code&gt;FlushAsync&lt;/code&gt; calls, and your implementation should choose a flush strategy based on the client and payload rather than assuming every write crosses the network immediately.&lt;/p&gt;
&lt;h2 id="headers-are-a-one-time-decision"&gt;Headers are a one-time decision&lt;/h2&gt;
&lt;p&gt;The API Gateway integration adds a constraint that is easy to miss: the response needs a prelude containing the HTTP status code and headers. Once the prelude has gone out, the handler cannot change the status code because a later exception occurs.&lt;/p&gt;
&lt;p&gt;That means validation and setup should happen before the first response bytes. If a model call can fail, decide how the stream will represent that failure after headers have been sent. A JSON error body cannot magically turn a previously sent &lt;code&gt;200&lt;/code&gt; into a &lt;code&gt;500&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The API Gateway integration also needs the response-streaming invocation path and the correct transfer mode. If the function is configured for streaming but the gateway still buffers, the client receives none of the latency benefit. Test the deployed path, not only the handler in isolation.&lt;/p&gt;
&lt;h2 id="timeouts-and-disconnects-become-visible"&gt;Timeouts and disconnects become visible&lt;/h2&gt;
&lt;p&gt;Streaming does not reset the Lambda timeout. A function with a 30-second timeout still has a 30-second budget, even if it flushes a token every second. API Gateway also needs its own timeout configuration and enough headroom for the integration to finish.&lt;/p&gt;
&lt;p&gt;Client disconnects create a second operational concern. If the browser closes after token 40, Lambda may continue generating token 41 through 200 unless the handler observes cancellation or a failed flush. That work costs money and may keep a model invocation alive after the user has gone away.&lt;/p&gt;
&lt;p&gt;Log the session or request ID, time to first byte, number of chunks sent, total duration, and the reason the stream ended. A normal completion, a client disconnect, and a model failure should not look identical in CloudWatch.&lt;/p&gt;
&lt;h2 id="aspnet-core-is-a-useful-path"&gt;ASP.NET Core is a useful path&lt;/h2&gt;
&lt;p&gt;For ASP.NET Core applications hosted on Lambda, the AWS hosting layer can enable response streaming with &lt;code&gt;EnableResponseStreaming&lt;/code&gt;. Standard response helpers can continue to work, but the integration still needs to be configured and tested.&lt;/p&gt;
&lt;p&gt;A minimal endpoint might write a line, flush periodically, and stop when the request is cancelled. Keep the stream format stable. For token output, newline-delimited events or a documented event format is easier for clients to process than arbitrary text fragments. For large exports, make the client aware that a partial response is not a valid completed file.&lt;/p&gt;
&lt;h2 id="where-the-feature-fits"&gt;Where the feature fits&lt;/h2&gt;
&lt;p&gt;The larger response ceiling is useful, but it should not become an excuse to send unbounded data through a synchronous Lambda request. A 200 MB limit does not make a long-lived download a good fit for every API. Use S3 for durable files and presigned delivery when that is the simpler contract.&lt;/p&gt;
&lt;p&gt;I would prioritize streaming for interactive AI responses and incremental exports where first-byte latency matters. For normal CRUD endpoints, buffering remains easier to observe and retry.&lt;/p&gt;
&lt;h2 id="what-to-test-before-production"&gt;What to test before production&lt;/h2&gt;
&lt;p&gt;Test the full path with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a slow model or generator,&lt;/li&gt;
&lt;li&gt;a client that disconnects halfway through,&lt;/li&gt;
&lt;li&gt;an exception after the first flush,&lt;/li&gt;
&lt;li&gt;a response that approaches the integration limit,&lt;/li&gt;
&lt;li&gt;API Gateway timeout headroom,&lt;/li&gt;
&lt;li&gt;retries after a partial response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also measure perceived latency rather than only total duration. The point of streaming is not to make the model compute less. It is to deliver useful output sooner without turning partial output into an ambiguous API.&lt;/p&gt;
&lt;p&gt;For .NET teams building serverless AI features, response streaming is a meaningful improvement. Just remember that once bytes leave the function, the response contract is already in motion. Headers, cancellation, observability, and client behavior need to be designed along with the &lt;code&gt;IAsyncEnumerable&lt;/code&gt; or stream itself.&lt;/p&gt;</content:encoded></item></channel></rss>