From fe85f48a15e0a56dbde7c20f927426322bcaacd7 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Mon, 24 Nov 2025 10:03:39 -0600
Subject: [PATCH 01/16] Initial example files.
---
.../.doc_gen/metadata/redshift_metadata.yaml | 209 +++++++++
dotnetv4/Redshift/Actions/HelloRedshift.cs | 68 +++
.../Redshift/Actions/RedshiftActions.csproj | 28 ++
dotnetv4/Redshift/Actions/RedshiftWrapper.cs | 433 ++++++++++++++++++
dotnetv4/Redshift/README.md | 129 ++++++
dotnetv4/Redshift/RedshiftExamples.sln | 37 ++
dotnetv4/Redshift/Scenarios/RedshiftBasics.cs | 314 +++++++++++++
.../Redshift/Scenarios/RedshiftBasics.csproj | 26 ++
.../Tests/RedshiftIntegrationTests.cs | 362 +++++++++++++++
dotnetv4/Redshift/Tests/RedshiftTests.csproj | 33 ++
steering_docs/dotnet-tech.md | 73 ++-
steering_docs/dotnet-tech/basics.md | 393 +++++++++++++++-
steering_docs/dotnet-tech/hello.md | 156 +++++--
steering_docs/dotnet-tech/tests.md | 405 ++++++++--------
steering_docs/dotnet-tech/wrapper.md | 147 +++---
15 files changed, 2481 insertions(+), 332 deletions(-)
create mode 100644 dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
create mode 100644 dotnetv4/Redshift/Actions/HelloRedshift.cs
create mode 100644 dotnetv4/Redshift/Actions/RedshiftActions.csproj
create mode 100644 dotnetv4/Redshift/Actions/RedshiftWrapper.cs
create mode 100644 dotnetv4/Redshift/README.md
create mode 100644 dotnetv4/Redshift/RedshiftExamples.sln
create mode 100644 dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
create mode 100644 dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj
create mode 100644 dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
create mode 100644 dotnetv4/Redshift/Tests/RedshiftTests.csproj
diff --git a/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml b/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
new file mode 100644
index 00000000000..10abfd4d7fc
--- /dev/null
+++ b/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
@@ -0,0 +1,209 @@
+# Amazon Redshift code examples for the SDK for .NET Framework 4.x
+
+redshift_CreateCluster:
+ title: Create an &RS; cluster
+ title_abbrev: Create a cluster
+ synopsis: create an &RS; cluster.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.CreateCluster
+ services:
+ redshift: {CreateCluster}
+
+redshift_DeleteCluster:
+ title: Delete an &RS; cluster
+ title_abbrev: Delete a cluster
+ synopsis: delete an &RS; cluster.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.DeleteCluster
+ services:
+ redshift: {DeleteCluster}
+
+redshift_DescribeClusters:
+ title: Describe &RS; clusters
+ title_abbrev: Describe clusters
+ synopsis: describe &RS; clusters.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.DescribeClusters
+ services:
+ redshift: {DescribeClusters}
+
+redshift_ModifyCluster:
+ title: Modify an &RS; cluster
+ title_abbrev: Modify a cluster
+ synopsis: modify an &RS; cluster.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.ModifyCluster
+ services:
+ redshift: {ModifyCluster}
+
+redshift_CreateTable:
+ title: Create a table in an &RS; database
+ title_abbrev: Create a table
+ synopsis: create a table in an &RS; database.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.CreateTable
+ services:
+ redshift-data: {ExecuteStatement}
+
+redshift_Insert:
+ title: Insert data into an &RS; table
+ title_abbrev: Insert data into a table
+ synopsis: insert data into an &RS; table.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.Insert
+ services:
+ redshift-data: {ExecuteStatement}
+
+redshift_Query:
+ title: Query data from an &RS; table
+ title_abbrev: Query data from a table
+ synopsis: query data from an &RS; table.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.Query
+ services:
+ redshift-data: {ExecuteStatement, GetStatementResult}
+
+redshift_DescribeStatement:
+ title: Describe an &RS; statement execution
+ title_abbrev: Describe a statement
+ synopsis: describe an &RS; statement execution.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.DescribeStatement
+ services:
+ redshift-data: {DescribeStatement}
+
+redshift_GetStatementResult:
+ title: Get results from an &RS; statement
+ title_abbrev: Get statement results
+ synopsis: get results from an &RS; statement.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.GetStatementResult
+ services:
+ redshift-data: {GetStatementResult}
+
+redshift_ListDatabases:
+ title: List databases in an &RS; cluster
+ title_abbrev: List databases
+ synopsis: list databases in an &RS; cluster.
+ category: Actions
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.ListDatabases
+ services:
+ redshift-data: {ListDatabases}
+
+redshift_Hello:
+ title: Hello &RS;
+ title_abbrev: Hello &RS;
+ synopsis: get started using &RS;.
+ category: Hello
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - dotnetv4.example_code.redshift.Hello
+ services:
+ redshift: {DescribeClusters}
+
+redshift_Scenario:
+ title: Get started with &RS; resources
+ title_abbrev: Get started with resources
+ synopsis: learn the basics of &RS; by creating clusters, databases, tables, and querying data.
+ category: Scenarios
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description: Create a Redshift wrapper class to manage operations.
+ snippet_tags:
+ - dotnetv4.example_code.redshift.RedshiftWrapper
+ - description: Run an interactive scenario demonstrating Redshift basics.
+ snippet_tags:
+ - dotnetv4.example_code.redshift.RedshiftScenario
+ services:
+ redshift: {CreateCluster, DeleteCluster, DescribeClusters, ModifyCluster}
+ redshift-data: {ExecuteStatement, DescribeStatement, GetStatementResult, ListDatabases}
diff --git a/dotnetv4/Redshift/Actions/HelloRedshift.cs b/dotnetv4/Redshift/Actions/HelloRedshift.cs
new file mode 100644
index 00000000000..56f2ef01d16
--- /dev/null
+++ b/dotnetv4/Redshift/Actions/HelloRedshift.cs
@@ -0,0 +1,68 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Amazon.Redshift;
+using Amazon.Redshift.Model;
+using Microsoft.Extensions.Logging;
+
+namespace RedshiftActions;
+
+///
+/// Hello Amazon Redshift example.
+///
+public class HelloRedshift
+{
+ private static ILogger logger = null!;
+
+ // snippet-start:[Redshift.dotnetv4.Hello]
+ ///
+ /// Main method to run the Hello Amazon Redshift example.
+ ///
+ /// Command line arguments (not used).
+ public static async Task Main(string[] args)
+ {
+ var redshiftClient = new AmazonRedshiftClient();
+
+ logger = LoggerFactory.Create(builder => { builder.AddConsole(); })
+ .CreateLogger();
+
+ Console.Clear();
+ Console.WriteLine("Hello, Amazon Redshift! Let's list available clusters:");
+ Console.WriteLine();
+
+ var clusters = new List();
+
+ try
+ {
+ // Use pagination to retrieve all clusters.
+ var clustersPaginator = redshiftClient.Paginators.DescribeClusters(new DescribeClustersRequest());
+
+ await foreach (var response in clustersPaginator.Responses)
+ {
+ if (response.Clusters != null)
+ clusters.AddRange(response.Clusters);
+ }
+
+ Console.WriteLine($"{clusters.Count} cluster(s) retrieved.");
+
+ foreach (var cluster in clusters)
+ {
+ Console.WriteLine($"\t{cluster.ClusterIdentifier} (Status: {cluster.ClusterStatus})");
+ }
+ }
+ catch (AmazonRedshiftException ex)
+ {
+ logger.LogError("Error listing clusters: {Message}", ex.Message);
+ Console.WriteLine($"Couldn't list clusters. Error: {ex.Message}");
+ }
+ catch (Exception ex)
+ {
+ logger.LogError("An error occurred: {Message}", ex.Message);
+ Console.WriteLine($"An error occurred: {ex.Message}");
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.Hello]
+}
diff --git a/dotnetv4/Redshift/Actions/RedshiftActions.csproj b/dotnetv4/Redshift/Actions/RedshiftActions.csproj
new file mode 100644
index 00000000000..899ba4b8047
--- /dev/null
+++ b/dotnetv4/Redshift/Actions/RedshiftActions.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ latest
+ RedshiftActions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ settings.json
+
+
+
diff --git a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
new file mode 100644
index 00000000000..e557ed1ffa8
--- /dev/null
+++ b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
@@ -0,0 +1,433 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Amazon.Redshift;
+using Amazon.Redshift.Model;
+using Amazon.RedshiftDataAPIService;
+using Amazon.RedshiftDataAPIService.Model;
+
+namespace RedshiftActions;
+
+// snippet-start:[Redshift.dotnetv4.RedshiftWrapper]
+///
+/// Wrapper class for Amazon Redshift operations.
+///
+public class RedshiftWrapper
+{
+ private readonly AmazonRedshiftClient _redshiftClient;
+ private readonly AmazonRedshiftDataAPIServiceClient _redshiftDataClient;
+
+ ///
+ /// Constructor for RedshiftWrapper.
+ ///
+ /// Amazon Redshift client.
+ /// Amazon Redshift Data API client.
+ public RedshiftWrapper(AmazonRedshiftClient redshiftClient, AmazonRedshiftDataAPIServiceClient redshiftDataClient)
+ {
+ _redshiftClient = redshiftClient;
+ _redshiftDataClient = redshiftDataClient;
+ }
+
+ // snippet-start:[Redshift.dotnetv4.CreateCluster]
+ ///
+ /// Create a new Amazon Redshift cluster.
+ ///
+ /// The identifier for the cluster.
+ /// The name of the database.
+ /// The master username.
+ /// The master user password.
+ /// The node type for the cluster.
+ /// The cluster that was created.
+ public async Task CreateClusterAsync(string clusterIdentifier, string databaseName,
+ string masterUsername, string masterUserPassword, string nodeType = "dc2.large")
+ {
+ try
+ {
+ var request = new CreateClusterRequest
+ {
+ ClusterIdentifier = clusterIdentifier,
+ DBName = databaseName,
+ MasterUsername = masterUsername,
+ MasterUserPassword = masterUserPassword,
+ NodeType = nodeType,
+ NumberOfNodes = 1,
+ ClusterType = "single-node"
+ };
+
+ var response = await _redshiftClient.CreateClusterAsync(request);
+ Console.WriteLine($"Created cluster {clusterIdentifier}");
+ return response.Cluster;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error creating cluster: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.CreateCluster]
+
+ // snippet-start:[Redshift.dotnetv4.DescribeClusters]
+ ///
+ /// Describe Amazon Redshift clusters.
+ ///
+ /// Optional cluster identifier to describe a specific cluster.
+ /// A list of clusters.
+ public async Task> DescribeClustersAsync(string? clusterIdentifier = null)
+ {
+ try
+ {
+ var request = new DescribeClustersRequest();
+ if (!string.IsNullOrEmpty(clusterIdentifier))
+ {
+ request.ClusterIdentifier = clusterIdentifier;
+ }
+
+ var response = await _redshiftClient.DescribeClustersAsync(request);
+ return response.Clusters;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error describing clusters: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.DescribeClusters]
+
+ // snippet-start:[Redshift.dotnetv4.ModifyCluster]
+ ///
+ /// Modify an Amazon Redshift cluster.
+ ///
+ /// The identifier for the cluster.
+ /// The preferred maintenance window.
+ /// The modified cluster.
+ public async Task ModifyClusterAsync(string clusterIdentifier, string preferredMaintenanceWindow)
+ {
+ try
+ {
+ var request = new ModifyClusterRequest
+ {
+ ClusterIdentifier = clusterIdentifier,
+ PreferredMaintenanceWindow = preferredMaintenanceWindow
+ };
+
+ var response = await _redshiftClient.ModifyClusterAsync(request);
+ Console.WriteLine($"The modified cluster was successfully modified and has {preferredMaintenanceWindow} as the maintenance window");
+ return response.Cluster;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error modifying cluster: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.ModifyCluster]
+
+ // snippet-start:[Redshift.dotnetv4.DeleteCluster]
+ ///
+ /// Delete an Amazon Redshift cluster.
+ ///
+ /// The identifier for the cluster.
+ /// The deleted cluster.
+ public async Task DeleteClusterAsync(string clusterIdentifier)
+ {
+ try
+ {
+ var request = new DeleteClusterRequest
+ {
+ ClusterIdentifier = clusterIdentifier,
+ SkipFinalClusterSnapshot = true
+ };
+
+ var response = await _redshiftClient.DeleteClusterAsync(request);
+ Console.WriteLine($"The {clusterIdentifier} was deleted");
+ return response.Cluster;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error deleting cluster: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.DeleteCluster]
+
+ // snippet-start:[Redshift.dotnetv4.ListDatabases]
+ ///
+ /// List databases in a Redshift cluster.
+ ///
+ /// The cluster identifier.
+ /// The database user.
+ /// A list of database names.
+ public async Task> ListDatabasesAsync(string clusterIdentifier, string dbUser)
+ {
+ try
+ {
+ var request = new ListDatabasesRequest
+ {
+ ClusterIdentifier = clusterIdentifier,
+ DbUser = dbUser
+ };
+
+ var response = await _redshiftDataClient.ListDatabasesAsync(request);
+ var databases = new List();
+
+ foreach (var database in response.Databases)
+ {
+ Console.WriteLine($"The database name is : {database}");
+ databases.Add(database);
+ }
+
+ return databases;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error listing databases: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.ListDatabases]
+
+ // snippet-start:[Redshift.dotnetv4.CreateTable]
+ ///
+ /// Create a table in the Redshift database.
+ ///
+ /// The cluster identifier.
+ /// The database name.
+ /// The database user.
+ /// The statement ID.
+ public async Task CreateTableAsync(string clusterIdentifier, string database, string dbUser)
+ {
+ try
+ {
+ var sqlStatement = @"
+ CREATE TABLE Movies (
+ id INTEGER PRIMARY KEY,
+ title VARCHAR(250) NOT NULL,
+ year INTEGER NOT NULL
+ )";
+
+ var request = new ExecuteStatementRequest
+ {
+ ClusterIdentifier = clusterIdentifier,
+ Database = database,
+ DbUser = dbUser,
+ Sql = sqlStatement
+ };
+
+ var response = await _redshiftDataClient.ExecuteStatementAsync(request);
+ await WaitForStatementToCompleteAsync(response.Id);
+ Console.WriteLine("Table created: Movies");
+ return response.Id;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error creating table: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.CreateTable]
+
+ // snippet-start:[Redshift.dotnetv4.Insert]
+ ///
+ /// Insert a record into the Movies table.
+ ///
+ /// The cluster identifier.
+ /// The database name.
+ /// The database user.
+ /// The movie ID.
+ /// The movie title.
+ /// The movie year.
+ /// The statement ID.
+ public async Task InsertMovieAsync(string clusterIdentifier, string database, string dbUser,
+ int id, string title, int year)
+ {
+ try
+ {
+ var sqlStatement = $"INSERT INTO Movies (id, title, year) VALUES ({id}, '{title}', {year})";
+
+ var request = new ExecuteStatementRequest
+ {
+ ClusterIdentifier = clusterIdentifier,
+ Database = database,
+ DbUser = dbUser,
+ Sql = sqlStatement
+ };
+
+ var response = await _redshiftDataClient.ExecuteStatementAsync(request);
+ await WaitForStatementToCompleteAsync(response.Id);
+ Console.WriteLine($"Inserted: {title} ({year})");
+ return response.Id;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error inserting movie: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.Insert]
+
+ // snippet-start:[Redshift.dotnetv4.Query]
+ ///
+ /// Query movies by year.
+ ///
+ /// The cluster identifier.
+ /// The database name.
+ /// The database user.
+ /// The year to query.
+ /// A list of movie titles.
+ public async Task> QueryMoviesByYearAsync(string clusterIdentifier, string database,
+ string dbUser, int year)
+ {
+ try
+ {
+ var sqlStatement = $"SELECT title FROM Movies WHERE year = {year}";
+
+ var request = new ExecuteStatementRequest
+ {
+ ClusterIdentifier = clusterIdentifier,
+ Database = database,
+ DbUser = dbUser,
+ Sql = sqlStatement
+ };
+
+ var response = await _redshiftDataClient.ExecuteStatementAsync(request);
+ Console.WriteLine($"The identifier of the statement is {response.Id}");
+
+ await WaitForStatementToCompleteAsync(response.Id);
+
+ var results = await GetStatementResultAsync(response.Id);
+ var movieTitles = new List();
+
+ foreach (var row in results)
+ {
+ if (row.Count > 0)
+ {
+ var title = row[0].StringValue;
+ Console.WriteLine($"The Movie title field is {title}");
+ movieTitles.Add(title);
+ }
+ }
+
+ return movieTitles;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error querying movies: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.Query]
+
+ // snippet-start:[Redshift.dotnetv4.DescribeStatement]
+ ///
+ /// Describe a statement execution.
+ ///
+ /// The statement ID.
+ /// The statement description.
+ public async Task DescribeStatementAsync(string statementId)
+ {
+ try
+ {
+ var request = new DescribeStatementRequest
+ {
+ Id = statementId
+ };
+
+ var response = await _redshiftDataClient.DescribeStatementAsync(request);
+ return response;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error describing statement: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.DescribeStatement]
+
+ // snippet-start:[Redshift.dotnetv4.GetStatementResult]
+ ///
+ /// Get the results of a statement execution.
+ ///
+ /// The statement ID.
+ /// A list of result rows.
+ public async Task>> GetStatementResultAsync(string statementId)
+ {
+ try
+ {
+ var request = new GetStatementResultRequest
+ {
+ Id = statementId
+ };
+
+ var response = await _redshiftDataClient.GetStatementResultAsync(request);
+ return response.Records;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error getting statement result: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[Redshift.dotnetv4.GetStatementResult]
+
+ ///
+ /// Wait for a statement to complete execution.
+ ///
+ /// The statement ID.
+ /// A task representing the asynchronous operation.
+ private async Task WaitForStatementToCompleteAsync(string statementId)
+ {
+ var status = StatusString.SUBMITTED;
+
+ while (status == StatusString.SUBMITTED || status == StatusString.PICKED || status == StatusString.STARTED)
+ {
+ await Task.Delay(1000); // Wait 1 second
+ var response = await DescribeStatementAsync(statementId);
+ status = response.Status;
+ Console.WriteLine($"...{status}");
+ }
+
+ if (status == StatusString.FINISHED)
+ {
+ Console.WriteLine("The statement is finished!");
+ }
+ else
+ {
+ Console.WriteLine($"The statement failed with status: {status}");
+ throw new Exception($"Statement execution failed with status: {status}");
+ }
+ }
+
+ ///
+ /// Wait for a cluster to become available.
+ ///
+ /// The cluster identifier.
+ /// A task representing the asynchronous operation.
+ public async Task WaitForClusterAvailableAsync(string clusterIdentifier)
+ {
+ Console.WriteLine($"Wait until {clusterIdentifier} is available.");
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+
+ Console.WriteLine("Waiting for cluster to become available. This may take a few minutes.");
+
+ var startTime = DateTime.Now;
+ var clusters = await DescribeClustersAsync(clusterIdentifier);
+
+ while (clusters[0].ClusterStatus != "available")
+ {
+ var elapsed = DateTime.Now - startTime;
+ Console.WriteLine($"Elapsed Time: {elapsed:mm\\:ss} - Waiting for cluster...");
+
+ await Task.Delay(5000); // Wait 5 seconds
+ clusters = await DescribeClustersAsync(clusterIdentifier);
+ }
+
+ var totalElapsed = DateTime.Now - startTime;
+ Console.WriteLine($"Cluster is available! Total Elapsed Time: {totalElapsed:mm\\:ss}");
+ }
+}
+// snippet-end:[Redshift.dotnetv4.RedshiftWrapper]
+
diff --git a/dotnetv4/Redshift/README.md b/dotnetv4/Redshift/README.md
new file mode 100644
index 00000000000..4b994bccafc
--- /dev/null
+++ b/dotnetv4/Redshift/README.md
@@ -0,0 +1,129 @@
+# Amazon Redshift code examples for the SDK for .NET Framework 4.x
+
+## Overview
+
+This folder contains code examples that demonstrate how to use the AWS SDK for .NET Framework 4.x to interact with Amazon Redshift.
+
+Amazon Redshift is a fast, fully managed, petabyte-scale data warehouse service that makes it simple and cost-effective to efficiently analyze all your data using your existing business intelligence tools.
+
+## ⚠ Important
+
+* Running this code might result in charges to your AWS account.
+* Running the tests might result in charges to your AWS account.
+* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).
+* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services).
+
+## Code examples
+
+### Actions
+
+Code excerpts that show you how to call individual service functions:
+
+- [CreateCluster](Actions/RedshiftWrapper.cs#L28) (`CreateCluster`)
+- [DeleteCluster](Actions/RedshiftWrapper.cs#L118) (`DeleteCluster`)
+- [DescribeClusters](Actions/RedshiftWrapper.cs#L69) (`DescribeClusters`)
+- [ModifyCluster](Actions/RedshiftWrapper.cs#L96) (`ModifyCluster`)
+- [CreateTable](Actions/RedshiftWrapper.cs#L157) (`ExecuteStatement`)
+- [InsertMovie](Actions/RedshiftWrapper.cs#L193) (`ExecuteStatement`)
+- [QueryMoviesByYear](Actions/RedshiftWrapper.cs#L227) (`ExecuteStatement`, `GetStatementResult`)
+- [DescribeStatement](Actions/RedshiftWrapper.cs#L269) (`DescribeStatement`)
+- [GetStatementResult](Actions/RedshiftWrapper.cs#L289) (`GetStatementResult`)
+- [ListDatabases](Actions/RedshiftWrapper.cs#L130) (`ListDatabases`)
+
+### Scenarios
+
+Code examples that show you how to accomplish a specific task by calling multiple functions within the same service:
+
+- [Get started with Redshift clusters](Scenarios/RedshiftBasics.cs) - Learn the basics of Amazon Redshift by creating a cluster, adding a table, inserting data, and querying the table.
+
+### Hello
+
+- [Hello Amazon Redshift](Hello/HelloRedshift.cs) - A simple example that shows how to get started with Amazon Redshift by listing existing clusters.
+
+## Run the examples
+
+### Prerequisites
+
+For general prerequisites, see the [README](../../README.md) in the `dotnetv4` folder.
+
+After the example compiles, you can run it from the command line. To do so, navigate to the folder that contains the .csproj file and run the following command:
+
+```
+dotnet run
+```
+
+Alternatively, you can run the example from within your IDE.
+
+### Hello Amazon Redshift
+
+This example shows you how to get started using Amazon Redshift.
+
+#### Purpose
+
+Shows how to use the AWS SDK for .NET to get started using Amazon Redshift. Lists existing Redshift clusters in your account.
+
+### Redshift Basics Scenario
+
+This scenario demonstrates how to interact with Amazon Redshift using the AWS SDK for .NET. It demonstrates various tasks such as creating a Redshift cluster, verifying its readiness, creating a table, populating the table with data, executing SQL queries, and finally cleaning up resources.
+
+#### Purpose
+
+Demonstrates how to:
+
+1. Create an Amazon Redshift cluster.
+2. Wait for the cluster to become available.
+3. List databases in the cluster.
+4. Create a "Movies" table.
+5. Populate the "Movies" table using sample data.
+6. Query the "Movies" table by year.
+7. Modify the Redshift cluster.
+8. Delete the Amazon Redshift cluster.
+
+#### Usage
+
+1. Clone the repository or download the source code files.
+2. Open the code in your preferred .NET IDE.
+3. Update the following variables in the `RunScenarioAsync()` method if needed:
+ - `userName`: The username for the Redshift cluster.
+ - `userPassword`: The password for the Redshift cluster.
+ - `databaseName`: The name of the database to use ("dev").
+4. Run the `RedshiftBasics` class.
+
+The program will guide you through the scenario, prompting you to enter a cluster ID and the number of records to add to the "Movies" table. The program will also display the progress and results of the various operations.
+
+## Tests
+
+### Unit tests
+
+Unit tests in this solution use MSTest. The tests use Moq to mock AWS service client dependencies.
+
+Run unit tests with this command:
+
+```
+dotnet test Tests/RedshiftTests.csproj
+```
+
+### Integration tests
+
+⚠️ Running the integration tests might result in charges to your AWS account.
+
+The integration tests in this solution require access to AWS services and will create and delete AWS resources. Make sure you have valid AWS credentials configured before running these tests.
+
+Run integration tests with this command:
+
+```
+dotnet test IntegrationTests/RedshiftIntegrationTests.csproj
+```
+
+Note: The full workflow integration test can take 10-15 minutes to complete due to cluster creation time.
+
+## Additional resources
+
+- [Amazon Redshift Management Guide](https://docs.aws.amazon.com/redshift/latest/mgmt/welcome.html)
+- [Amazon Redshift API Reference](https://docs.aws.amazon.com/redshift/latest/APIReference/Welcome.html)
+- [SDK for .NET Amazon Redshift reference](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/Redshift/NRedshift.html)
+
+---
+
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
diff --git a/dotnetv4/Redshift/RedshiftExamples.sln b/dotnetv4/Redshift/RedshiftExamples.sln
new file mode 100644
index 00000000000..ca60478e539
--- /dev/null
+++ b/dotnetv4/Redshift/RedshiftExamples.sln
@@ -0,0 +1,37 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33414.496
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RedshiftActions", "Actions\RedshiftActions.csproj", "{A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RedshiftBasics", "Scenarios\RedshiftBasics.csproj", "{C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RedshiftTests", "Tests\RedshiftTests.csproj", "{D4E5F6A7-B8C9-4D5E-9F1A-4B5C6D7E8F9A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B2C3D4-E5F6-4A5B-8C9D-1E2F3A4B5C6D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C3D4E5F6-A7B8-4C5D-9E1F-3A4B5C6D7E8F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4E5F6A7-B8C9-4D5E-9F1A-4B5C6D7E8F9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4E5F6A7-B8C9-4D5E-9F1A-4B5C6D7E8F9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4E5F6A7-B8C9-4D5E-9F1A-4B5C6D7E8F9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4E5F6A7-B8C9-4D5E-9F1A-4B5C6D7E8F9A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}
+ EndGlobalSection
+EndGlobal
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
new file mode 100644
index 00000000000..d6fb9c82a49
--- /dev/null
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
@@ -0,0 +1,314 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Amazon.Redshift;
+using Amazon.RedshiftDataAPIService;
+using RedshiftActions;
+
+namespace RedshiftBasics;
+
+// snippet-start:[Redshift.dotnetv4.RedshiftScenario]
+///
+/// Amazon Redshift Getting Started Scenario.
+///
+public class RedshiftBasics
+{
+ private static RedshiftWrapper? _redshiftWrapper;
+ private static readonly string MoviesFilePath = "../../../../../../../resources/sample_files/movies.json";
+
+ ///
+ /// Main method for the Amazon Redshift Getting Started scenario.
+ ///
+ /// Command line arguments.
+ public static async Task Main(string[] args)
+ {
+ // Initialize the Amazon Redshift clients
+ var redshiftClient = new AmazonRedshiftClient();
+ var redshiftDataClient = new AmazonRedshiftDataAPIServiceClient();
+ _redshiftWrapper = new RedshiftWrapper(redshiftClient, redshiftDataClient);
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Welcome to the Amazon Redshift SDK Getting Started scenario.");
+ Console.WriteLine("This .NET program demonstrates how to interact with Amazon Redshift by using the AWS SDK for .NET.");
+ Console.WriteLine("Amazon Redshift is a fully managed, petabyte-scale data warehouse service hosted in the cloud.");
+ Console.WriteLine("The program's primary functionality includes cluster creation, verification of cluster readiness,");
+ Console.WriteLine("list databases, table creation, data population within the table, and execution of SQL statements.");
+ Console.WriteLine("Furthermore, it demonstrates the process of querying data from the Movie table.");
+ Console.WriteLine("Upon completion of the program, all AWS resources are cleaned up.");
+ Console.WriteLine("Let's get started...");
+ Console.WriteLine("================================================================================");
+
+ try
+ {
+ await RunScenarioAsync();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"An error occurred: {ex.Message}");
+ Console.WriteLine(ex.StackTrace);
+ }
+ finally
+ {
+ redshiftClient.Dispose();
+ redshiftDataClient.Dispose();
+ }
+ }
+
+ ///
+ /// Run the complete Amazon Redshift scenario.
+ ///
+ private static async Task RunScenarioAsync()
+ {
+ // Step 1: Get user credentials
+ Console.WriteLine("Please enter your user name (default is awsuser):");
+ var userName = Console.ReadLine();
+ if (string.IsNullOrEmpty(userName))
+ userName = "awsuser";
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Please enter your user password (default is AwsUser1000):");
+ var userPassword = Console.ReadLine();
+ if (string.IsNullOrEmpty(userPassword))
+ userPassword = "AwsUser1000";
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("A Redshift cluster refers to the collection of computing resources and storage that work together to process and analyze large volumes of data.");
+
+ // Step 2: Get cluster identifier
+ Console.WriteLine("Enter a cluster id value (default is redshift-cluster-movies):");
+ var clusterIdentifier = Console.ReadLine();
+ if (string.IsNullOrEmpty(clusterIdentifier))
+ clusterIdentifier = "redshift-cluster-movies";
+
+ var databaseName = "dev";
+
+ try
+ {
+ // Step 3: Create Redshift cluster
+ await _redshiftWrapper!.CreateClusterAsync(clusterIdentifier, databaseName, userName, userPassword);
+ Console.WriteLine("================================================================================");
+
+ // Step 4: Wait for cluster to become available
+ Console.WriteLine("================================================================================");
+ await _redshiftWrapper.WaitForClusterAvailableAsync(clusterIdentifier);
+ Console.WriteLine("================================================================================");
+
+ // Step 5: List databases
+ Console.WriteLine("================================================================================");
+ Console.WriteLine($" When you created {clusterIdentifier}, the dev database is created by default and used in this scenario.");
+ Console.WriteLine(" To create a custom database, you need to have a CREATEDB privilege.");
+ Console.WriteLine(" For more information, see the documentation here: https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_DATABASE.html.");
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ Console.WriteLine("================================================================================");
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine($"List databases in {clusterIdentifier}");
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ await _redshiftWrapper.ListDatabasesAsync(clusterIdentifier, userName);
+ Console.WriteLine("================================================================================");
+
+ // Step 6: Create Movies table
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Now you will create a table named Movies.");
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ await _redshiftWrapper.CreateTableAsync(clusterIdentifier, databaseName, userName);
+ Console.WriteLine("================================================================================");
+
+ // Step 7: Populate the Movies table
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Populate the Movies table using the Movies.json file.");
+ Console.WriteLine("Specify the number of records you would like to add to the Movies Table.");
+ Console.WriteLine("Please enter a value between 50 and 200.");
+ Console.Write("Enter a value: ");
+
+ var recordCountInput = Console.ReadLine();
+ if (!int.TryParse(recordCountInput, out var recordCount) || recordCount < 50 || recordCount > 200)
+ {
+ recordCount = 50;
+ Console.WriteLine($"Invalid input. Using default value of {recordCount}.");
+ }
+
+ await PopulateMoviesTableAsync(clusterIdentifier, databaseName, userName, recordCount);
+ Console.WriteLine($"{recordCount} records were added to the Movies table.");
+ Console.WriteLine("================================================================================");
+
+ // Step 8 & 9: Query movies by year
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Query the Movies table by year. Enter a value between 2012-2014.");
+ Console.Write("Enter a year: ");
+ var yearInput = Console.ReadLine();
+ if (!int.TryParse(yearInput, out var year) || year < 2012 || year > 2014)
+ {
+ year = 2013;
+ Console.WriteLine($"Invalid input. Using default value of {year}.");
+ }
+
+ await _redshiftWrapper.QueryMoviesByYearAsync(clusterIdentifier, databaseName, userName, year);
+ Console.WriteLine("================================================================================");
+
+ // Step 10: Modify the cluster
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Now you will modify the Redshift cluster.");
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ await _redshiftWrapper.ModifyClusterAsync(clusterIdentifier, "wed:07:30-wed:08:00");
+ Console.WriteLine("================================================================================");
+
+ // Step 11 & 12: Delete cluster confirmation
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Would you like to delete the Amazon Redshift cluster? (y/n)");
+ var deleteResponse = Console.ReadLine();
+ if (deleteResponse?.ToLower() == "y" || deleteResponse?.ToLower() == "yes")
+ {
+ await _redshiftWrapper.DeleteClusterAsync(clusterIdentifier);
+ }
+ Console.WriteLine("================================================================================");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"An error occurred during the scenario: {ex.Message}");
+
+ // Attempt cleanup
+ Console.WriteLine("Attempting to clean up resources...");
+ try
+ {
+ await _redshiftWrapper!.DeleteClusterAsync(clusterIdentifier);
+ }
+ catch (Exception cleanupEx)
+ {
+ Console.WriteLine($"Cleanup failed: {cleanupEx.Message}");
+ }
+ throw;
+ }
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("This concludes the Amazon Redshift SDK Getting Started scenario.");
+ Console.WriteLine("================================================================================");
+ }
+
+ ///
+ /// Populate the Movies table with data from the JSON file.
+ ///
+ /// The cluster identifier.
+ /// The database name.
+ /// The database user.
+ /// Number of records to insert.
+ private static async Task PopulateMoviesTableAsync(string clusterIdentifier, string database, string dbUser, int recordCount)
+ {
+ try
+ {
+ if (!File.Exists(MoviesFilePath))
+ {
+ Console.WriteLine($"Movies file not found at {MoviesFilePath}. Using sample data instead.");
+ await PopulateWithSampleDataAsync(clusterIdentifier, database, dbUser, recordCount);
+ return;
+ }
+
+ var jsonContent = await File.ReadAllTextAsync(MoviesFilePath);
+ var movies = JsonSerializer.Deserialize>(jsonContent);
+
+ if (movies == null)
+ {
+ Console.WriteLine("Failed to parse movies JSON file. Using sample data instead.");
+ await PopulateWithSampleDataAsync(clusterIdentifier, database, dbUser, recordCount);
+ return;
+ }
+
+ var insertCount = Math.Min(recordCount, movies.Count);
+
+ for (int i = 0; i < insertCount; i++)
+ {
+ var movie = movies[i];
+ await _redshiftWrapper!.InsertMovieAsync(clusterIdentifier, database, dbUser, movie.Id, movie.Title, movie.Year);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error populating movies table: {ex.Message}");
+ Console.WriteLine("Using sample data instead.");
+ await PopulateWithSampleDataAsync(clusterIdentifier, database, dbUser, recordCount);
+ }
+ }
+
+ ///
+ /// Populate the table with sample movie data when JSON file is not available.
+ ///
+ /// The cluster identifier.
+ /// The database name.
+ /// The database user.
+ /// Number of records to insert.
+ private static async Task PopulateWithSampleDataAsync(string clusterIdentifier, string database, string dbUser, int recordCount)
+ {
+ var sampleMovies = new List
+ {
+ new Movie { Id = 1, Title = "Rush", Year = 2013 },
+ new Movie { Id = 2, Title = "Prisoners", Year = 2013 },
+ new Movie { Id = 3, Title = "The Hunger Games: Catching Fire", Year = 2013 },
+ new Movie { Id = 4, Title = "Thor: The Dark World", Year = 2013 },
+ new Movie { Id = 5, Title = "This Is the End", Year = 2013 },
+ new Movie { Id = 6, Title = "Despicable Me 2", Year = 2013 },
+ new Movie { Id = 7, Title = "Man of Steel", Year = 2013 },
+ new Movie { Id = 8, Title = "Gravity", Year = 2013 },
+ new Movie { Id = 9, Title = "Pacific Rim", Year = 2013 },
+ new Movie { Id = 10, Title = "World War Z", Year = 2013 },
+ new Movie { Id = 11, Title = "Iron Man 3", Year = 2013 },
+ new Movie { Id = 12, Title = "Star Trek Into Darkness", Year = 2013 },
+ new Movie { Id = 13, Title = "Fast & Furious 6", Year = 2013 },
+ new Movie { Id = 14, Title = "Monsters University", Year = 2013 },
+ new Movie { Id = 15, Title = "Elysium", Year = 2013 },
+ new Movie { Id = 16, Title = "The Hobbit: The Desolation of Smaug", Year = 2013 },
+ new Movie { Id = 17, Title = "Captain Phillips", Year = 2013 },
+ new Movie { Id = 18, Title = "Ender's Game", Year = 2013 },
+ new Movie { Id = 19, Title = "The Wolverine", Year = 2013 },
+ new Movie { Id = 20, Title = "Now You See Me", Year = 2013 }
+ };
+
+ // Generate more movies if needed
+ var movieList = new List(sampleMovies);
+ var random = new Random();
+ var years = new[] { 2012, 2013, 2014 };
+ var baseMovieTitles = new[] { "Action Movie", "Drama Film", "Comedy Show", "Thriller Movie", "Adventure Film", "Sci-Fi Movie", "Horror Film" };
+
+ while (movieList.Count < recordCount)
+ {
+ var baseTitle = baseMovieTitles[random.Next(baseMovieTitles.Length)];
+ var year = years[random.Next(years.Length)];
+ var movie = new Movie
+ {
+ Id = movieList.Count + 1,
+ Title = $"{baseTitle} {movieList.Count + 1}",
+ Year = year
+ };
+ movieList.Add(movie);
+ }
+
+ var insertCount = Math.Min(recordCount, movieList.Count);
+
+ for (int i = 0; i < insertCount; i++)
+ {
+ var movie = movieList[i];
+ await _redshiftWrapper!.InsertMovieAsync(clusterIdentifier, database, dbUser, movie.Id, movie.Title, movie.Year);
+ }
+ }
+
+ ///
+ /// Movie data model.
+ ///
+ private class Movie
+ {
+ public int Id { get; set; }
+ public string Title { get; set; } = string.Empty;
+ public int Year { get; set; }
+ }
+}
+// snippet-end:[Redshift.dotnetv4.RedshiftScenario]
+
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj b/dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj
new file mode 100644
index 00000000000..bf152493dec
--- /dev/null
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ latest
+ RedshiftBasics
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ settings.json
+
+
+
diff --git a/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
new file mode 100644
index 00000000000..f10e0b4cbcd
--- /dev/null
+++ b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
@@ -0,0 +1,362 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Threading.Tasks;
+using Amazon.Redshift;
+using Amazon.RedshiftDataAPIService;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using RedshiftActions;
+
+namespace RedshiftTests;
+
+///
+/// Integration tests for Amazon Redshift operations.
+/// These tests require actual AWS credentials and will create real AWS resources.
+///
+[TestClass]
+public class RedshiftIntegrationTests
+{
+ private static RedshiftWrapper? _redshiftWrapper;
+ private static string? _testClusterIdentifier;
+ private const string TestDatabaseName = "dev";
+ private const string TestUsername = "testuser";
+ private const string TestPassword = "TestPassword123!";
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ // Initialize clients
+ var redshiftClient = new AmazonRedshiftClient();
+ var redshiftDataClient = new AmazonRedshiftDataAPIServiceClient();
+ _redshiftWrapper = new RedshiftWrapper(redshiftClient, redshiftDataClient);
+
+ // Generate unique cluster identifier
+ _testClusterIdentifier = $"test-cluster-{DateTime.Now:yyyyMMddHHmmss}";
+
+ Console.WriteLine($"Integration tests will use cluster: {_testClusterIdentifier}");
+ }
+
+ [ClassCleanup]
+ public static async Task ClassCleanup()
+ {
+ // Clean up any remaining test resources
+ if (_redshiftWrapper != null && !string.IsNullOrEmpty(_testClusterIdentifier))
+ {
+ try
+ {
+ Console.WriteLine($"Cleaning up test cluster: {_testClusterIdentifier}");
+ await _redshiftWrapper.DeleteClusterAsync(_testClusterIdentifier);
+ Console.WriteLine("Test cluster cleanup initiated.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Warning: Failed to cleanup test cluster: {ex.Message}");
+ }
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Integration")]
+ public async Task DescribeClusters_Integration_ReturnsClusterList()
+ {
+ // Act
+ var clusters = await _redshiftWrapper!.DescribeClustersAsync();
+
+ // Assert
+ Assert.IsNotNull(clusters);
+ // Note: We don't assert specific count since other clusters might exist
+ Console.WriteLine($"Found {clusters.Count} existing clusters.");
+ }
+
+ [TestMethod]
+ [TestCategory("Integration")]
+ [TestCategory("LongRunning")]
+ public async Task RedshiftFullWorkflow_Integration_CompletesSuccessfully()
+ {
+ // This test runs the complete Redshift workflow
+ // Note: This test can take 10-15 minutes to complete due to cluster creation time
+
+ try
+ {
+ Console.WriteLine("Starting Redshift full workflow integration test...");
+
+ // Step 1: Create cluster
+ Console.WriteLine($"Creating cluster: {_testClusterIdentifier}");
+ var createdCluster = await _redshiftWrapper!.CreateClusterAsync(
+ _testClusterIdentifier!,
+ TestDatabaseName,
+ TestUsername,
+ TestPassword);
+
+ Assert.IsNotNull(createdCluster);
+ Assert.AreEqual(_testClusterIdentifier, createdCluster.ClusterIdentifier);
+ Console.WriteLine("Cluster creation initiated successfully.");
+
+ // Step 2: Wait for cluster to become available (this can take several minutes)
+ Console.WriteLine("Waiting for cluster to become available... This may take 10-15 minutes.");
+ await WaitForClusterAvailable(_testClusterIdentifier!, TimeSpan.FromMinutes(20));
+
+ // Step 3: List databases
+ Console.WriteLine("Listing databases...");
+ var databases = await _redshiftWrapper.ListDatabasesAsync(_testClusterIdentifier!, TestUsername);
+ Assert.IsNotNull(databases);
+ Assert.IsTrue(databases.Count > 0);
+ Console.WriteLine($"Found {databases.Count} databases.");
+
+ // Step 4: Create table
+ Console.WriteLine("Creating Movies table...");
+ var createTableStatementId = await _redshiftWrapper.CreateTableAsync(
+ _testClusterIdentifier!,
+ TestDatabaseName,
+ TestUsername);
+ Assert.IsNotNull(createTableStatementId);
+ Console.WriteLine("Movies table created successfully.");
+
+ // Step 5: Insert sample data
+ Console.WriteLine("Inserting sample movie data...");
+ var insertStatementId = await _redshiftWrapper.InsertMovieAsync(
+ _testClusterIdentifier!,
+ TestDatabaseName,
+ TestUsername,
+ 1,
+ "Test Movie",
+ 2023);
+ Assert.IsNotNull(insertStatementId);
+ Console.WriteLine("Sample data inserted successfully.");
+
+ // Step 6: Query data
+ Console.WriteLine("Querying movies by year...");
+ var movies = await _redshiftWrapper.QueryMoviesByYearAsync(
+ _testClusterIdentifier!,
+ TestDatabaseName,
+ TestUsername,
+ 2023);
+ Assert.IsNotNull(movies);
+ Assert.IsTrue(movies.Count > 0);
+ Assert.AreEqual("Test Movie", movies[0]);
+ Console.WriteLine($"Query returned {movies.Count} movies.");
+
+ // Step 7: Modify cluster
+ Console.WriteLine("Modifying cluster maintenance window...");
+ var modifiedCluster = await _redshiftWrapper.ModifyClusterAsync(
+ _testClusterIdentifier!,
+ "wed:07:30-wed:08:00");
+ Assert.IsNotNull(modifiedCluster);
+ Console.WriteLine("Cluster modified successfully.");
+
+ Console.WriteLine("Full workflow integration test completed successfully!");
+ }
+ finally
+ {
+ // Step 8: Clean up - Delete cluster
+ if (!string.IsNullOrEmpty(_testClusterIdentifier))
+ {
+ Console.WriteLine($"Deleting test cluster: {_testClusterIdentifier}");
+ try
+ {
+ var deletedCluster = await _redshiftWrapper!.DeleteClusterAsync(_testClusterIdentifier);
+ Assert.IsNotNull(deletedCluster);
+ Console.WriteLine("Cluster deletion initiated successfully.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to delete cluster: {ex.Message}");
+ throw;
+ }
+ }
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Integration")]
+ public async Task DescribeStatement_Integration_ReturnsStatementDetails()
+ {
+ // This test requires an existing cluster - skip if none available
+ var clusters = await _redshiftWrapper!.DescribeClustersAsync();
+
+ if (clusters.Count == 0)
+ {
+ Assert.Inconclusive("No Redshift clusters available for integration testing.");
+ return;
+ }
+
+ var testCluster = clusters[0];
+ if (testCluster.ClusterStatus != "available")
+ {
+ Assert.Inconclusive($"Test cluster {testCluster.ClusterIdentifier} is not available (status: {testCluster.ClusterStatus}).");
+ return;
+ }
+
+ try
+ {
+ // Execute a simple statement
+ var statementId = await _redshiftWrapper.CreateTableAsync(
+ testCluster.ClusterIdentifier,
+ TestDatabaseName,
+ TestUsername);
+
+ // Describe the statement
+ var statementDetails = await _redshiftWrapper.DescribeStatementAsync(statementId);
+
+ Assert.IsNotNull(statementDetails);
+ Assert.AreEqual(statementId, statementDetails.Id);
+ Assert.IsNotNull(statementDetails.Status);
+
+ Console.WriteLine($"Statement {statementId} has status: {statementDetails.Status}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Integration test failed: {ex.Message}");
+ Assert.Inconclusive($"Could not complete integration test: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Wait for a cluster to become available with timeout.
+ ///
+ /// The cluster identifier.
+ /// Maximum time to wait.
+ private async Task WaitForClusterAvailable(string clusterIdentifier, TimeSpan timeout)
+ {
+ var startTime = DateTime.UtcNow;
+ var endTime = startTime.Add(timeout);
+
+ while (DateTime.UtcNow < endTime)
+ {
+ var clusters = await _redshiftWrapper!.DescribeClustersAsync(clusterIdentifier);
+
+ if (clusters.Count > 0 && clusters[0].ClusterStatus == "available")
+ {
+ Console.WriteLine($"Cluster {clusterIdentifier} is now available!");
+ return;
+ }
+
+ var elapsed = DateTime.UtcNow - startTime;
+ Console.WriteLine($"Waiting for cluster... Elapsed time: {elapsed:mm\\:ss}");
+
+ await Task.Delay(TimeSpan.FromSeconds(30)); // Wait 30 seconds between checks
+ }
+
+ throw new TimeoutException($"Cluster {clusterIdentifier} did not become available within {timeout.TotalMinutes} minutes.");
+ }
+}
+
+///
+/// Integration tests specifically for data operations.
+/// These tests require an existing, available Redshift cluster.
+///
+[TestClass]
+public class RedshiftDataIntegrationTests
+{
+ private static RedshiftWrapper? _redshiftWrapper;
+ private const string TestDatabaseName = "dev";
+ private const string TestUsername = "testuser";
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ var redshiftClient = new AmazonRedshiftClient();
+ var redshiftDataClient = new AmazonRedshiftDataAPIServiceClient();
+ _redshiftWrapper = new RedshiftWrapper(redshiftClient, redshiftDataClient);
+ }
+
+ [TestMethod]
+ [TestCategory("Integration")]
+ [TestCategory("DataOperations")]
+ public async Task DataOperations_WithExistingCluster_WorksCorrectly()
+ {
+ // Find an available cluster for testing
+ var clusters = await _redshiftWrapper!.DescribeClustersAsync();
+ var availableCluster = clusters.Find(c => c.ClusterStatus == "available");
+
+ if (availableCluster == null)
+ {
+ Assert.Inconclusive("No available Redshift clusters found for data operations testing.");
+ return;
+ }
+
+ var clusterIdentifier = availableCluster.ClusterIdentifier;
+ Console.WriteLine($"Using cluster: {clusterIdentifier}");
+
+ try
+ {
+ // Test creating a unique table
+ var tableName = $"test_table_{DateTime.Now:yyyyMMddHHmmss}";
+ Console.WriteLine($"Creating table: {tableName}");
+
+ // Note: This would require modifying the wrapper to accept custom table names
+ // For now, we'll test with the standard Movies table
+
+ var createResult = await _redshiftWrapper.CreateTableAsync(
+ clusterIdentifier,
+ TestDatabaseName,
+ TestUsername);
+
+ Assert.IsNotNull(createResult);
+ Console.WriteLine("Table creation test completed.");
+
+ // Test data insertion
+ var insertResult = await _redshiftWrapper.InsertMovieAsync(
+ clusterIdentifier,
+ TestDatabaseName,
+ TestUsername,
+ 999,
+ $"Integration Test Movie {DateTime.Now:HHmmss}",
+ 2023);
+
+ Assert.IsNotNull(insertResult);
+ Console.WriteLine("Data insertion test completed.");
+
+ // Test data querying
+ var queryResults = await _redshiftWrapper.QueryMoviesByYearAsync(
+ clusterIdentifier,
+ TestDatabaseName,
+ TestUsername,
+ 2023);
+
+ Assert.IsNotNull(queryResults);
+ Console.WriteLine($"Query returned {queryResults.Count} results.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Data operations test failed: {ex.Message}");
+ // Don't fail the test for expected database-related issues
+ Assert.Inconclusive($"Data operations test could not complete: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ [TestCategory("Integration")]
+ public async Task ListDatabases_WithExistingCluster_ReturnsResults()
+ {
+ var clusters = await _redshiftWrapper!.DescribeClustersAsync();
+ var availableCluster = clusters.Find(c => c.ClusterStatus == "available");
+
+ if (availableCluster == null)
+ {
+ Assert.Inconclusive("No available Redshift clusters found for database listing test.");
+ return;
+ }
+
+ try
+ {
+ var databases = await _redshiftWrapper.ListDatabasesAsync(
+ availableCluster.ClusterIdentifier,
+ TestUsername);
+
+ Assert.IsNotNull(databases);
+ Console.WriteLine($"Found {databases.Count} databases in cluster {availableCluster.ClusterIdentifier}");
+
+ foreach (var db in databases)
+ {
+ Console.WriteLine($" Database: {db}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"List databases test failed: {ex.Message}");
+ Assert.Inconclusive($"Could not list databases: {ex.Message}");
+ }
+ }
+}
diff --git a/dotnetv4/Redshift/Tests/RedshiftTests.csproj b/dotnetv4/Redshift/Tests/RedshiftTests.csproj
new file mode 100644
index 00000000000..9e1b2b84028
--- /dev/null
+++ b/dotnetv4/Redshift/Tests/RedshiftTests.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net8.0
+ false
+ enable
+ latest
+ RedshiftTests
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ settings.json
+
+
+
diff --git a/steering_docs/dotnet-tech.md b/steering_docs/dotnet-tech.md
index 290814eaf05..4b6b8aca1f7 100644
--- a/steering_docs/dotnet-tech.md
+++ b/steering_docs/dotnet-tech.md
@@ -45,12 +45,16 @@ dotnet format # Format code
- **Documentation**: Include XML documentation explaining the hello example purpose
#### Code Structure Standards
-- **Namespace naming**: Use reverse domain notation (e.g., `Amazon.DocSamples.S3`)
+- **Namespace naming**: Use file-scoped namespaces (e.g., `namespace RedshiftActions;`)
- **Class structure**: One public class per file matching filename
- **Method naming**: Use PascalCase for method names
- **Properties**: Use PascalCase for property names
- **Constants**: Use PascalCase for constants
- **Async methods**: Suffix with `Async` (e.g., `ListBucketsAsync`)
+- **Indentation**: Use 4 spaces (no tabs), proper indentation after file-scoped namespace
+- **Target Framework**: .NET 8.0 (`net8.0`)
+- **Language Version**: Latest (`latest`)
+- **Snippet tags**: Use format `[{Service}.dotnetv4.{ActionName}]` (e.g., `[Redshift.dotnetv4.CreateCluster]`)
#### Dependency Injection Patterns
```csharp
@@ -130,17 +134,28 @@ public class ExampleClass
#### Project Structure
```
-src/
-├── {Service}Examples/
-│ ├── Hello{Service}.cs
-│ ├── {Service}Actions.cs
-│ ├── {Service}Scenarios.cs
-│ └── {Service}Examples.csproj
-└── {Service}Examples.Tests/
- ├── {Service}Tests.cs
- └── {Service}Examples.Tests.csproj
+dotnetv4/{Service}/
+├── Actions/
+│ ├── Hello{Service}.cs # Hello example (with Main method)
+│ ├── {Service}Wrapper.cs # Service wrapper class
+│ └── {Service}Actions.csproj # Actions project file
+├── Scenarios/
+│ ├── {Service}Basics.cs # Basics scenario
+│ └── {Service}Basics.csproj # Scenarios project file
+├── Tests/
+│ ├── {Service}IntegrationTests.cs # Integration tests only
+│ └── {Service}Tests.csproj # Test project file
+└── {Service}Examples.sln # Service-specific solution file
```
+**CRITICAL Project Organization Rules:**
+- ✅ **Hello examples MUST be in the Actions project** with a Main method
+- ✅ **Integration tests ONLY** - no separate unit tests for wrapper methods
+- ✅ **No separate Hello project** - Hello is part of Actions
+- ✅ **No separate IntegrationTests project** - all tests in Tests project
+- ✅ **Create a service-specific solution file** named {Service}Examples.sln
+- ✅ **Solution must include**: Actions, Scenarios, and Tests projects only
+
#### Documentation Requirements
- **XML documentation**: Use `///` for class and method documentation
- **Parameter documentation**: Document all parameters with ``
@@ -176,21 +191,49 @@ src/
- ✅ **ALWAYS create examples in the dotnetv4 directory unless instructed otherwise**
- ✅ **ALWAYS follow the established .NET project structure**
- ✅ **ALWAYS use PascalCase for .NET identifiers**
+- ✅ **ALWAYS use file-scoped namespaces** (namespace Name; instead of namespace Name { })
- ✅ **ALWAYS use using statements for AWS client management**
- ✅ **ALWAYS include proper exception handling for AWS service calls**
- ✅ **ALWAYS test AWS credentials before assuming credential issues**
- ✅ **ALWAYS include comprehensive XML documentation**
- ✅ **ALWAYS use async/await patterns for AWS operations**
- ✅ **ALWAYS use dependency injection for AWS services**
-- ✅ **ALWAYS create a separate class in the Actions project for the Hello example**
-- ✅ **ALWAYS add project files to the main solution file DotNetV4Examples.sln**
+- ✅ **ALWAYS create Hello example in the Actions project** with a Main method
+- ✅ **ALWAYS create a service-specific solution file** (e.g., Redshift.sln)
+- ✅ **ALWAYS target .NET 8.0** with latest language version
+- ✅ **ALWAYS put integration tests in the Tests project** (no separate IntegrationTests project)
- ✅ **ALWAYS put print statements in the action methods if possible**
+- ✅ **ALWAYS update package versions** to avoid NU1603 warnings
### Project Configuration Requirements
-- **Target Framework**: Specify appropriate .NET version in .csproj
-- **AWS SDK packages**: Include specific AWS service NuGet packages
-- **Test packages**: Include xUnit and test runner packages
+- **Target Framework**: .NET 8.0 (`net8.0`)
+- **Language Version**: Latest (`latest`)
+- **AWS SDK packages**: Include specific AWS service NuGet packages (use latest versions)
+- **Test packages**: Include MSTest and test runner packages
- **Configuration**: Support for appsettings.json and environment variables
+- **Nullable**: Enable nullable reference types (`enable`)
+
+### Solution File Management
+Each service should have its own solution file in the service directory:
+
+```bash
+# Create solution file
+dotnet new sln -n {Service}Examples -o dotnetv4/{Service}
+
+# Add projects to solution
+dotnet sln dotnetv4/{Service}/{Service}Examples.sln add dotnetv4/{Service}/Actions/{Service}Actions.csproj
+dotnet sln dotnetv4/{Service}/{Service}Examples.sln add dotnetv4/{Service}/Scenarios/{Service}Basics.csproj
+dotnet sln dotnetv4/{Service}/{Service}Examples.sln add dotnetv4/{Service}/Tests/{Service}Tests.csproj
+
+# Build solution
+dotnet build dotnetv4/{Service}/{Service}Examples.sln
+```
+
+**Solution Structure:**
+- ✅ **3 projects only**: Actions, Scenarios, Tests
+- ✅ **No solution folders** - flat structure
+- ✅ **Service-specific naming**: {Service}Examples.sln (e.g., RedshiftExamples.sln)
+- ✅ **Located in service directory**: dotnetv4/{Service}/{Service}Examples.sln
### Integration with Knowledge Base
Before creating .NET code examples:
diff --git a/steering_docs/dotnet-tech/basics.md b/steering_docs/dotnet-tech/basics.md
index 98a94e968bc..0e2a71b1753 100644
--- a/steering_docs/dotnet-tech/basics.md
+++ b/steering_docs/dotnet-tech/basics.md
@@ -392,4 +392,395 @@ catch (Amazon{Service}Exception ex)
- Show before/after states as outlined in specification
- Provide context about service capabilities from specification
- Include tips and best practices mentioned in specification
-- Follow the educational flow described in specification structure
\ No newline at end of file
+- Follow the educational flow described in specification structure
+
+---
+
+#
+.NET v4 Project Organization Standards
+
+## Overview
+This section outlines the standardized project structure and organization for .NET v4 AWS SDK examples, based on the Redshift implementation.
+
+## Project Structure
+
+### Directory Layout
+```
+dotnetv4/{Service}/
+├── Actions/
+│ ├── Hello{Service}.cs # Hello example (with Main method)
+│ ├── {Service}Wrapper.cs # Service wrapper class
+│ └── {Service}Actions.csproj # Actions project file
+├── Scenarios/
+│ ├── {Service}Basics.cs # Basics scenario
+│ └── {Service}Basics.csproj # Scenarios project file
+├── Tests/
+│ ├── {Service}IntegrationTests.cs # Integration tests
+│ └── {Service}Tests.csproj # Test project file
+└── {Service}Examples.sln # Service-specific solution file
+```
+
+## Critical Organization Rules
+
+### ✅ DO
+- **Create service-specific solution files** in the service directory (e.g., `RedshiftExamples.sln`)
+- **Name solution files** as `{Service}Examples.sln` for consistency
+- **Include Hello example in Actions project** with a Main method
+- **Put integration tests in Tests project** (no separate IntegrationTests project)
+- **Use 3 projects only**: Actions, Scenarios, Tests
+- **Use flat solution structure** (no solution folders)
+- **Target .NET 8.0** with latest language version
+- **Use file-scoped namespaces** (namespace Name; instead of namespace Name { })
+- **Update package versions** to latest to avoid NU1603 warnings
+
+### ❌ DON'T
+- **Don't create separate Hello project** - it belongs in Actions
+- **Don't create separate IntegrationTests project** - use Tests project
+- **Don't use solution folders** - keep flat structure
+- **Don't target .NET Framework 4.8** - use .NET 8.0
+- **Don't use traditional namespaces** - use file-scoped
+- **Don't create unit tests with mocks** - use integration tests only
+
+## Project Configuration
+
+### Actions Project (.csproj)
+```xml
+
+
+
+ Exe
+ net8.0
+ enable
+ latest
+ {Service}Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ settings.json
+
+
+
+```
+
+**Key Points:**
+- `Exe` - Required for Hello example Main method
+- `net8.0` - Use .NET 8.0
+- `latest` - Enable latest C# features
+- Include Microsoft.Extensions packages for dependency injection in Hello example
+- Use latest stable AWS SDK package versions (3.7.500 for AWS services)
+
+### Scenarios Project (.csproj)
+```xml
+
+
+
+ Exe
+ net8.0
+ enable
+ latest
+ {Service}Basics
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ settings.json
+
+
+
+```
+
+### Tests Project (.csproj)
+```xml
+
+
+
+ net8.0
+ false
+ enable
+ latest
+ {Service}Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+ settings.json
+
+
+
+```
+
+**Key Points:**
+- Use MSTest framework (not xUnit)
+- No `` - tests are libraries
+- Reference Actions project only
+- Use latest stable test framework versions
+
+## Solution File Management
+
+### Creating Solution File
+```bash
+# Navigate to service directory
+cd dotnetv4/{Service}
+
+# Create solution file
+dotnet new sln -n {Service}Examples
+
+# Add projects
+dotnet sln {Service}Examples.sln add Actions/{Service}Actions.csproj
+dotnet sln {Service}Examples.sln add Scenarios/{Service}Basics.csproj
+dotnet sln {Service}Examples.sln add Tests/{Service}Tests.csproj
+
+# Build solution
+dotnet build {Service}Examples.sln
+```
+
+### Solution File Structure
+```
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.33414.496
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "{Service}Actions", "Actions\{Service}Actions.csproj", "{GUID1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "{Service}Basics", "Scenarios\{Service}Basics.csproj", "{GUID2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "{Service}Tests", "Tests\{Service}Tests.csproj", "{GUID3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {GUID1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {GUID1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {GUID1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {GUID1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {GUID2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {GUID2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {GUID2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {GUID2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {GUID3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {GUID3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {GUID3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {GUID3}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {SOLUTION-GUID}
+ EndGlobalSection
+EndGlobal
+```
+
+## Code Style Standards
+
+### File-Scoped Namespaces
+```csharp
+// ✅ CORRECT - File-scoped namespace
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Threading.Tasks;
+using Amazon.Redshift;
+
+namespace RedshiftActions;
+
+public class HelloRedshift
+{
+ public static async Task Main(string[] args)
+ {
+ // Implementation
+ }
+}
+```
+
+```csharp
+// ❌ WRONG - Traditional namespace
+namespace RedshiftActions
+{
+ public class HelloRedshift
+ {
+ public static async Task Main(string[] args)
+ {
+ // Implementation
+ }
+ }
+}
+```
+
+### Indentation
+- Use 4 spaces (no tabs)
+- No extra indentation after file-scoped namespace
+- Consistent indentation throughout
+
+### Snippet Tags
+**CRITICAL**: Use the correct snippet tag format for all code examples:
+
+```csharp
+// ✅ CORRECT - Service name first, then dotnetv4
+// snippet-start:[Redshift.dotnetv4.CreateCluster]
+public async Task CreateClusterAsync(...)
+{
+ // Implementation
+}
+// snippet-end:[Redshift.dotnetv4.CreateCluster]
+
+// ❌ WRONG - Old format
+// snippet-start:[dotnetv4.example_code.redshift.CreateCluster]
+```
+
+**Format**: `[{Service}.dotnetv4.{ActionName}]`
+- Service name in PascalCase (e.g., Redshift, S3, DynamoDB)
+- Followed by `.dotnetv4.`
+- Action name in PascalCase (e.g., CreateCluster, ListBuckets, Hello)
+- For wrapper class: `[{Service}.dotnetv4.{Service}Wrapper]`
+- For scenarios: `[{Service}.dotnetv4.{Service}Scenario]`
+
+## Testing Standards
+
+### Integration Tests Only
+- **No unit tests with mocks** - test against real AWS services
+- **Use MSTest framework** - not xUnit
+- **Test complete workflows** - not individual methods
+- **Proper resource cleanup** - use ClassCleanup method
+- **Use TestCategory attributes** - for test organization
+
+### Test Class Structure
+```csharp
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Threading.Tasks;
+using Amazon.Redshift;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using RedshiftActions;
+
+namespace RedshiftTests;
+
+[TestClass]
+public class RedshiftIntegrationTests
+{
+ private static RedshiftWrapper? _redshiftWrapper;
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
+ {
+ // Initialize clients
+ }
+
+ [ClassCleanup]
+ public static async Task ClassCleanup()
+ {
+ // Clean up resources
+ }
+
+ [TestMethod]
+ [TestCategory("Integration")]
+ public async Task TestOperation()
+ {
+ // Test implementation
+ }
+}
+```
+
+## Migration Checklist
+
+When updating existing projects to new standards:
+
+- [ ] Create service-specific solution file
+- [ ] Move Hello example to Actions project
+- [ ] Add `Exe` to Actions project
+- [ ] Consolidate integration tests into Tests project
+- [ ] Remove separate IntegrationTests project
+- [ ] Update all projects to target .NET 8.0
+- [ ] Add `latest` to all projects
+- [ ] Convert all namespaces to file-scoped
+- [ ] Fix indentation (remove extra level after namespace)
+- [ ] Update AWS SDK package versions to latest
+- [ ] Remove solution folders (use flat structure)
+- [ ] Update test framework to MSTest if using xUnit
+- [ ] Remove unit tests with mocks
+- [ ] Verify solution builds without warnings
+
+## Build and Test Commands
+
+```bash
+# Build solution
+dotnet build dotnetv4/{Service}/{Service}Examples.sln
+
+# Run all tests
+dotnet test dotnetv4/{Service}/{Service}Examples.sln
+
+# Run integration tests only
+dotnet test dotnetv4/{Service}/Tests/{Service}Tests.csproj --filter TestCategory=Integration
+
+# Run Hello example
+dotnet run --project dotnetv4/{Service}/Actions/{Service}Actions.csproj
+
+# Run Basics scenario
+dotnet run --project dotnetv4/{Service}/Scenarios/{Service}Basics.csproj
+```
+
+## Common Issues and Solutions
+
+### Issue: Package version warnings (NU1603)
+**Solution:** Update all AWS SDK packages to latest version (e.g., 3.7.401)
+
+### Issue: C# language version errors
+**Solution:** Add `latest` to all project files
+
+### Issue: Hello example won't run
+**Solution:** Ensure Actions project has `Exe`
+
+### Issue: Build errors after namespace conversion
+**Solution:** Check indentation - no extra level after file-scoped namespace
+
+### Issue: Tests not discovered
+**Solution:** Ensure using MSTest attributes ([TestClass], [TestMethod])
+
+## References
+- Main steering doc: `steering_docs/dotnet-tech.md`
+- Hello examples: `steering_docs/dotnet-tech/hello.md`
+- Tests: `steering_docs/dotnet-tech/tests.md`
+- Wrapper: `steering_docs/dotnet-tech/wrapper.md`
diff --git a/steering_docs/dotnet-tech/hello.md b/steering_docs/dotnet-tech/hello.md
index 2d50ef4a0c8..0b29ba1d278 100644
--- a/steering_docs/dotnet-tech/hello.md
+++ b/steering_docs/dotnet-tech/hello.md
@@ -32,7 +32,28 @@ Generate simple "Hello" examples that demonstrate basic service connectivity and
## File Structure
```
dotnetv4/{Service}/Actions/
-├── Hello{Service}.cs # Hello example file
+├── Hello{Service}.cs # Hello example file (with Main method)
+├── {Service}Wrapper.cs # Service wrapper class
+└── {Service}Actions.csproj # Actions project file (OutputType=Exe)
+```
+
+**CRITICAL:**
+- ✅ Hello example MUST be in the Actions project
+- ✅ Actions project MUST have `Exe` to support Main method
+- ✅ NO separate Hello project should be created
+- ✅ Actions project MUST include Microsoft.Extensions packages for dependency injection
+
+**Required NuGet Packages for Actions Project:**
+```xml
+
+
+
+
+
+
+
+
+
```
## Hello Example Pattern
@@ -40,59 +61,83 @@ dotnetv4/{Service}/Actions/
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-///
-/// Purpose
-///
-/// Shows how to get started with {AWS Service} by {basic operation description}.
-///
-
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using Amazon.{Service};
using Amazon.{Service}.Model;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Logging.Debug;
+
+namespace {Service}Actions;
-namespace Amazon.DocSamples.{Service}
+///
+/// Hello Amazon {Service} example.
+///
+public class Hello{Service}
{
- public class Hello{Service}
+ private static ILogger logger = null!;
+
+ // snippet-start:[{Service}.dotnetv4.Hello]
+ ///
+ /// Main method to run the Hello Amazon {Service} example.
+ ///
+ /// Command line arguments (not used).
+ public static async Task Main(string[] args)
{
- ///
- /// Use the AWS SDK for .NET to create an {AWS Service} client and
- /// {basic operation description}.
- /// This example uses the default settings specified in your shared credentials
- /// and config files.
- ///
- /// Command line arguments.
- public static async Task Main(string[] args)
+ // Set up dependency injection for Amazon {Service}.
+ using var host = Host.CreateDefaultBuilder(args)
+ .ConfigureLogging(logging =>
+ logging.AddFilter("System", LogLevel.Debug)
+ .AddFilter("Microsoft", LogLevel.Information)
+ .AddFilter("Microsoft", LogLevel.Trace))
+ .ConfigureServices((_, services) =>
+ services.AddAWSService())
+ .Build();
+
+ logger = LoggerFactory.Create(builder => { builder.AddConsole(); })
+ .CreateLogger();
+
+ var {service}Client = host.Services.GetRequiredService();
+
+ Console.Clear();
+ Console.WriteLine("Hello, Amazon {Service}! Let's list available {resources}:");
+ Console.WriteLine();
+
+ var {resources} = new List<{Resource}>();
+
+ try
{
- try
- {
- // Create service client
- using var {service}Client = new Amazon{Service}Client();
-
- // Perform the most basic operation for this service
- var response = await {service}Client.{BasicOperation}Async();
-
- Console.WriteLine("Hello, {AWS Service}!");
- // Display appropriate result information
-
- }
- catch (Amazon{Service}Exception ex)
+ // Use pagination to retrieve all {resources}
+ var {resources}Paginator = {service}Client.Paginators.List{Resources}(new List{Resources}Request());
+
+ await foreach (var response in {resources}Paginator.Responses)
{
- if (ex.ErrorCode == "UnauthorizedOperation")
- {
- Console.WriteLine("You don't have permission to access {AWS Service}.");
- }
- else
- {
- Console.WriteLine($"Couldn't access {AWS Service}. Error: {ex.Message}");
- }
+ {resources}.AddRange(response.{Resources});
}
- catch (Exception ex)
+
+ Console.WriteLine($"{{{resources}.Count}} {resource}(s) retrieved.");
+
+ foreach (var {resource} in {resources})
{
- Console.WriteLine($"An unexpected error occurred: {ex.Message}");
+ Console.WriteLine($"\t{{{resource}.Name}}");
}
}
+ catch (Amazon{Service}Exception ex)
+ {
+ logger.LogError("Error listing {resources}: {Message}", ex.Message);
+ Console.WriteLine($"Couldn't list {resources}. Error: {ex.Message}");
+ }
+ catch (Exception ex)
+ {
+ logger.LogError("An error occurred: {Message}", ex.Message);
+ Console.WriteLine($"An error occurred: {ex.Message}");
+ }
}
+ // snippet-end:[{Service}.dotnetv4.Hello]
}
```
@@ -114,13 +159,36 @@ namespace Amazon.DocSamples.{Service}
- ✅ **Must run without errors** (with proper credentials)
- ✅ **Must handle credential issues gracefully**
- ✅ **Must display meaningful output**
-- ✅ **Must use direct AWS SDK for .NET client calls**
+- ✅ **Must use dependency injection with Host.CreateDefaultBuilder**
+- ✅ **Must use pagination for list operations**
- ✅ **Must include proper XML documentation**
+- ✅ **Must use correct snippet tag format**: `[{Service}.dotnetv4.Hello]`
## Common Patterns
-- Always use `new Amazon{Service}Client()` directly
+- Use dependency injection with Host.CreateDefaultBuilder
+- Use pagination to retrieve all results
- Include comprehensive error handling with try-catch blocks
- Provide user-friendly output messages using Console.WriteLine
- Handle both service-specific and general exceptions
-- Keep it as simple as possible - no additional classes or complexity
-- Use async/await pattern for all AWS operations
\ No newline at end of file
+- Keep it as simple as possible - all logic in Main method
+- Use async/await pattern for all AWS operations
+
+## Snippet Tag Format
+**CRITICAL**: Use the correct snippet tag format:
+
+```csharp
+// ✅ CORRECT
+// snippet-start:[Redshift.dotnetv4.Hello]
+public static async Task Main(string[] args)
+{
+ // Implementation
+}
+// snippet-end:[Redshift.dotnetv4.Hello]
+
+// ❌ WRONG - Old format
+// snippet-start:[dotnetv4.example_code.redshift.Hello]
+```
+
+**Format**: `[{Service}.dotnetv4.Hello]`
+- Service name in PascalCase (e.g., Redshift, S3, DynamoDB)
+- Always use `.dotnetv4.Hello` for Hello examples
\ No newline at end of file
diff --git a/steering_docs/dotnet-tech/tests.md b/steering_docs/dotnet-tech/tests.md
index 6576e7ba8c9..b66e437a5ae 100644
--- a/steering_docs/dotnet-tech/tests.md
+++ b/steering_docs/dotnet-tech/tests.md
@@ -21,300 +21,295 @@ read_documentation("https://docs.aws.amazon.com/[service]/latest/[relevant-page]
**FAILURE TO COMPLETE KNOWLEDGE BASE CONSULTATION WILL RESULT IN INCORRECT CODE STRUCTURE**
## Purpose
-Generate comprehensive test suites including unit tests and integration tests using xUnit framework with proper mocking and AWS data structures.
+Generate integration test suites using MSTest framework to validate complete scenario workflows against real AWS services.
## Requirements
-- **Complete Data**: Use complete AWS data structures in tests
-- **Proper Attributes**: Use xUnit attributes for test categorization
-- **Error Coverage**: Test all error conditions from specification
+- **Integration Tests Only**: Focus on end-to-end scenario testing, not unit tests
+- **Real AWS Services**: Tests run against actual AWS infrastructure
+- **Proper Attributes**: Use MSTest attributes for test categorization
- **Async Testing**: Use async Task for async test methods
+- **Resource Cleanup**: Ensure proper cleanup of AWS resources after tests
## File Structure
```
dotnetv4/{Service}/Tests/
-├── {Service}Tests.csproj # Test project file
-├── {Service}Tests.cs # Unit and integration tests
+├── {Service}Tests.csproj # Test project file
+├── {Service}IntegrationTests.cs # Integration tests
```
+**CRITICAL:**
+- ✅ **Integration tests ONLY** - no separate unit tests for wrapper methods
+- ✅ **All tests in one project** - no separate IntegrationTests project
+- ✅ **Use MSTest framework** - not xUnit
+- ✅ **Test against real AWS** - not mocks
+
## Test Project Setup
### Step 1: Create Test Project File
```xml
+
-
net8.0
- enable
- enable
false
- true
+ enable
+ latest
+ {Service}Tests
-
-
-
+
+
+
-
-
+
+
-
-
+
+
+
+ PreserveNewest
+ settings.json
+
+
```
-### Step 2: Create Test Class Structure
+**Key Changes:**
+- ✅ Use MSTest packages instead of xUnit
+- ✅ Target .NET 8.0 with latest language version
+- ✅ Use latest AWS SDK package versions
+- ✅ Reference Actions project only (no Scenarios reference needed)
+
+### Step 2: Create Integration Test Class Structure
```csharp
-// dotnetv4/{Service}/Tests/{Service}Tests.cs
+// dotnetv4/{Service}/Tests/{Service}IntegrationTests.cs
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
using System;
using System.Threading.Tasks;
using Amazon.{Service};
-using Amazon.{Service}.Model;
-using Moq;
-using Xunit;
-using Xunit.Abstractions;
+using Amazon.{ServiceDataAPI};
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using {Service}Actions;
+
+namespace {Service}Tests;
-namespace Amazon.DocSamples.{Service}.Tests
+///
+/// Integration tests for Amazon {Service} operations.
+/// These tests require actual AWS credentials and will create real AWS resources.
+///
+[TestClass]
+public class {Service}IntegrationTests
{
- public class {Service}Tests
+ private static {Service}Wrapper? _{service}Wrapper;
+ private static string? _testResourceIdentifier;
+ private const string TestDatabaseName = "dev";
+ private const string TestUsername = "testuser";
+ private const string TestPassword = "TestPassword123!";
+
+ [ClassInitialize]
+ public static void ClassInitialize(TestContext context)
{
- private readonly ITestOutputHelper _output;
- private readonly Mock _mock{Service}Client;
- private readonly {Service}Wrapper _wrapper;
+ // Initialize clients
+ var {service}Client = new Amazon{Service}Client();
+ var {service}DataClient = new Amazon{ServiceDataAPI}Client();
+ _{service}Wrapper = new {Service}Wrapper({service}Client, {service}DataClient);
+
+ // Generate unique resource identifier
+ _testResourceIdentifier = $"test-resource-{DateTime.Now:yyyyMMddHHmmss}";
+
+ Console.WriteLine($"Integration tests will use resource: {_testResourceIdentifier}");
+ }
- public {Service}Tests(ITestOutputHelper output)
+ [ClassCleanup]
+ public static async Task ClassCleanup()
+ {
+ // Clean up any remaining test resources
+ if (_{service}Wrapper != null && !string.IsNullOrEmpty(_testResourceIdentifier))
{
- _output = output;
- _mock{Service}Client = new Mock();
- _wrapper = new {Service}Wrapper(_mock{Service}Client.Object);
+ try
+ {
+ Console.WriteLine($"Cleaning up test resource: {_testResourceIdentifier}");
+ await _{service}Wrapper.DeleteResourceAsync(_testResourceIdentifier);
+ Console.WriteLine("Test resource cleanup initiated.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Warning: Failed to cleanup test resource: {ex.Message}");
+ }
}
}
}
```
-## Unit Test Pattern
-```csharp
-[Theory]
-[InlineData(null)]
-[InlineData("BadRequestException")]
-[InlineData("InternalServerErrorException")]
-public async Task Test{ActionName}Async_WithVariousConditions_ReturnsExpectedResult(string? errorCode)
-{
- // Arrange
- var paramValue = "test-value";
- var expectedResponse = new {ActionName}Response
- {
- ResponseKey = "response-value"
- };
-
- if (errorCode == null)
- {
- _mock{Service}Client
- .Setup(x => x.{ActionName}Async(It.IsAny<{ActionName}Request>(), default))
- .ReturnsAsync(expectedResponse);
- }
- else
- {
- _mock{Service}Client
- .Setup(x => x.{ActionName}Async(It.IsAny<{ActionName}Request>(), default))
- .ThrowsAsync(new Amazon{Service}Exception(errorCode));
- }
-
- // Act & Assert
- if (errorCode == null)
- {
- var result = await _wrapper.{ActionName}Async(paramValue);
- Assert.Equal("response-value", result.ResponseKey);
- }
- else
- {
- var exception = await Assert.ThrowsAsync(
- () => _wrapper.{ActionName}Async(paramValue));
- Assert.Equal(errorCode, exception.ErrorCode);
- }
-}
-```
+**Key Changes:**
+- ✅ Use file-scoped namespaces
+- ✅ Use MSTest attributes ([TestClass], [TestMethod], [ClassInitialize], [ClassCleanup])
+- ✅ No mocking - test against real AWS services
+- ✅ Include proper resource cleanup in ClassCleanup
-## Complete AWS Data Structures
+## Integration Test Patterns
-### CRITICAL: Use Complete AWS Response Data
+### Basic Integration Test
```csharp
-// ❌ WRONG - Minimal data that fails validation
-var findings = new List
-{
- new Finding { Id = "finding-1", Type = "SomeType", Severity = 8.0 }
-};
-
-// ✅ CORRECT - Complete AWS data structure
-var findings = new List
+[TestMethod]
+[TestCategory("Integration")]
+public async Task DescribeResources_Integration_ReturnsResourceList()
{
- new Finding
- {
- Id = "finding-1",
- AccountId = "123456789012",
- Arn = "arn:aws:service:region:account:resource/id",
- Type = "SomeType",
- Severity = 8.0,
- CreatedAt = DateTime.Parse("2023-01-01T00:00:00.000Z"),
- UpdatedAt = DateTime.Parse("2023-01-01T00:00:00.000Z"),
- Region = "us-east-1",
- SchemaVersion = "2.0",
- Resource = new Resource { ResourceType = "Instance" }
- }
-};
-```
-
-## Integration Test Pattern
-Create a single integration test for the scenario by setting IsInteractive to false:
-
-```csharp
-using {Service}Basics;
-using Microsoft.Extensions.Logging;
-using Moq;
-using Xunit;
+ // Act
+ var resources = await _{service}Wrapper!.DescribeResourcesAsync();
-namespace {Service}Tests;
-
-///
-/// Integration tests for the Amazon {Service} Basics scenario.
-///
-public class {Service}BasicsTest
-{
- ///
- /// Verifies the scenario with an integration test. No errors should be logged.
- ///
- /// A task representing the asynchronous test operation.
- [Fact]
- [Trait("Category", "Integration")]
- public async Task TestScenario()
- {
- // Arrange.
- {Service}Basics.IsInteractive = false;
- var loggerMock = new Mock>();
- loggerMock.Setup(logger => logger.Log(
- It.Is(logLevel => logLevel == LogLevel.Error),
- It.IsAny(),
- It.Is((@object, @type) => true),
- It.IsAny(),
- It.IsAny>()));
-
- // Act.
- await {Service}Basics.Main(new string[] { "" });
-
- // Assert no exceptions or errors logged.
- loggerMock.Verify(logger => logger.Log(
- It.Is(logLevel => logLevel == LogLevel.Error),
- It.IsAny(),
- It.Is((@object, @type) => true),
- It.IsAny(),
- It.IsAny>()),
- Times.Never);
- }
+ // Assert
+ Assert.IsNotNull(resources);
+ // Note: We don't assert specific count since other resources might exist
+ Console.WriteLine($"Found {resources.Count} existing resources.");
}
```
-## Additional Integration Test Patterns (Optional)
-If needed, you can add specific wrapper method tests:
-
+### Full Workflow Integration Test
```csharp
-[Fact]
-[Trait("Category", "Integration")]
-public async Task Test{ActionName}Integration()
-{
- // Arrange
- var wrapper = new {Service}Wrapper(new Amazon{Service}Client());
-
- // Act - This should not raise an exception
- var result = await wrapper.{ActionName}Async();
-
- // Assert - Verify result structure
- Assert.NotNull(result);
-}
-
-[Fact]
-[Trait("Category", "Integration")]
-public async Task TestResourceLifecycleIntegration()
+[TestMethod]
+[TestCategory("Integration")]
+[TestCategory("LongRunning")]
+public async Task {Service}FullWorkflow_Integration_CompletesSuccessfully()
{
- // Arrange
- var wrapper = new {Service}Wrapper(new Amazon{Service}Client());
- string? resourceId = null;
+ // This test runs the complete {Service} workflow
+ // Note: This test can take 10-15 minutes to complete
try
{
- // Act - Create resource
- resourceId = await wrapper.CreateResourceAsync();
- Assert.NotNull(resourceId);
+ Console.WriteLine("Starting {Service} full workflow integration test...");
- // Use resource
- var result = await wrapper.GetResourceAsync(resourceId);
- Assert.NotNull(result);
+ // Step 1: Create resource
+ Console.WriteLine($"Creating resource: {_testResourceIdentifier}");
+ var createdResource = await _{service}Wrapper!.CreateResourceAsync(
+ _testResourceIdentifier!,
+ TestDatabaseName,
+ TestUsername,
+ TestPassword);
+
+ Assert.IsNotNull(createdResource);
+ Assert.AreEqual(_testResourceIdentifier, createdResource.Identifier);
+ Console.WriteLine("Resource creation initiated successfully.");
+
+ // Step 2: Wait for resource to become available
+ Console.WriteLine("Waiting for resource to become available...");
+ await WaitForResourceAvailable(_testResourceIdentifier!, TimeSpan.FromMinutes(20));
+
+ // Step 3: Perform operations
+ Console.WriteLine("Performing operations...");
+ var result = await _{service}Wrapper.PerformOperationAsync(_testResourceIdentifier!);
+ Assert.IsNotNull(result);
+ Console.WriteLine("Operations completed successfully.");
+
+ Console.WriteLine("Full workflow integration test completed successfully!");
}
finally
{
- // Clean up
- if (!string.IsNullOrEmpty(resourceId))
+ // Clean up - Delete resource
+ if (!string.IsNullOrEmpty(_testResourceIdentifier))
{
+ Console.WriteLine($"Deleting test resource: {_testResourceIdentifier}");
try
{
- await wrapper.DeleteResourceAsync(resourceId);
+ var deletedResource = await _{service}Wrapper!.DeleteResourceAsync(_testResourceIdentifier);
+ Assert.IsNotNull(deletedResource);
+ Console.WriteLine("Resource deletion initiated successfully.");
}
- catch
+ catch (Exception ex)
{
- // Ignore cleanup errors
+ Console.WriteLine($"Failed to delete resource: {ex.Message}");
+ throw;
}
}
}
}
+
+///
+/// Wait for a resource to become available with timeout.
+///
+/// The resource identifier.
+/// Maximum time to wait.
+private async Task WaitForResourceAvailable(string resourceIdentifier, TimeSpan timeout)
+{
+ var startTime = DateTime.UtcNow;
+ var endTime = startTime.Add(timeout);
+
+ while (DateTime.UtcNow < endTime)
+ {
+ var resources = await _{service}Wrapper!.DescribeResourcesAsync(resourceIdentifier);
+
+ if (resources.Count > 0 && resources[0].Status == "available")
+ {
+ Console.WriteLine($"Resource {resourceIdentifier} is now available!");
+ return;
+ }
+
+ var elapsed = DateTime.UtcNow - startTime;
+ Console.WriteLine($"Waiting for resource... Elapsed time: {elapsed:mm\\:ss}");
+
+ await Task.Delay(TimeSpan.FromSeconds(30)); // Wait 30 seconds between checks
+ }
+
+ throw new TimeoutException($"Resource {resourceIdentifier} did not become available within {timeout.TotalMinutes} minutes.");
+}
```
## Test Execution Commands
-### Unit Tests
+### All Tests
+```bash
+dotnet test dotnetv4/{Service}/{Service}Examples.sln
+```
+
+### Integration Tests Only
```bash
-dotnet test dotnetv4/{Service}/Tests/{Service}Tests.csproj --filter "Category!=Integration"
+dotnet test dotnetv4/{Service}/Tests/{Service}Tests.csproj --filter TestCategory=Integration
```
-### Integration Tests (Runs Complete Scenario)
+### Exclude Long-Running Tests
```bash
-dotnet test dotnetv4/{Service}/Tests/{Service}Tests.csproj --filter Category=Integration
+dotnet test dotnetv4/{Service}/Tests/{Service}Tests.csproj --filter "TestCategory=Integration&TestCategory!=LongRunning"
```
## Test Requirements Checklist
- ✅ **Test project file created** with proper dependencies
-- ✅ **Mock framework setup** (Moq for mocking AWS clients and loggers)
-- ✅ **Complete AWS data structures** in all tests
-- ✅ **Proper xUnit attributes** (`[Trait("Category", "Integration")]`)
-- ✅ **Error condition coverage** per specification
-- ✅ **Integration test sets IsInteractive to false** for automated testing
-- ✅ **Logger verification** to ensure no errors are logged
+- ✅ **MSTest framework** (not xUnit)
+- ✅ **Integration tests only** (no unit tests with mocks)
+- ✅ **Proper MSTest attributes** (`[TestCategory("Integration")]`)
+- ✅ **Test against real AWS services** (not mocks)
+- ✅ **Proper resource cleanup** in ClassCleanup method
- ✅ **Async test methods** using `async Task`
+- ✅ **File-scoped namespaces** for modern C# style
## Integration Test Focus
-### Primary Test: Complete Scenario Integration
+### Primary Test: Complete Workflow Integration
The main integration test should:
-- ✅ **Run the entire scenario** from start to finish
-- ✅ **Set IsInteractive to false** to automate the interactive parts
-- ✅ **Test against real AWS** services (not mocks)
-- ✅ **Verify no errors are logged** using mock logger verification
-- ✅ **Handle cleanup automatically** through scenario logic
+- ✅ **Run the entire workflow** from start to finish
+- ✅ **Test against real AWS services** (not mocks)
+- ✅ **Create and clean up resources** properly
+- ✅ **Handle long-running operations** with appropriate timeouts
+- ✅ **Use TestCategory attributes** for test organization
### Test Structure Priority
-1. **Integration Test** - Most important, tests complete workflow
-2. **Unit Tests** - Test individual wrapper methods
-3. **Additional Integration Tests** - Optional, for specific edge cases
+1. **Full Workflow Integration Test** - Most important, tests complete workflow
+2. **Basic Operation Tests** - Test individual operations against real AWS
+3. **Resource Lifecycle Tests** - Test create, read, update, delete operations
## Common Test Failures to Avoid
-- ❌ **Not setting IsInteractive to false** in scenario integration tests
-- ❌ **Using incomplete AWS data structures** in unit tests
-- ❌ **Missing xUnit attributes** for integration tests
-- ❌ **Not verifying logger mock** to ensure no errors are logged
+- ❌ **Using mocks instead of real AWS services** in integration tests
+- ❌ **Missing MSTest attributes** for integration tests
+- ❌ **Not cleaning up resources** in ClassCleanup
- ❌ **Forgetting to set AWS region** in test clients
-- ❌ **Not testing all error conditions** from specification
-- ❌ **Not calling Main method properly** in scenario integration tests
-- ❌ **Missing proper async/await patterns** in test methods
\ No newline at end of file
+- ❌ **Not handling long-running operations** with timeouts
+- ❌ **Missing proper async/await patterns** in test methods
+- ❌ **Using xUnit instead of MSTest** framework
+- ❌ **Creating separate IntegrationTests project** instead of using Tests project
\ No newline at end of file
diff --git a/steering_docs/dotnet-tech/wrapper.md b/steering_docs/dotnet-tech/wrapper.md
index fe858234bfe..b56d599e2e3 100644
--- a/steering_docs/dotnet-tech/wrapper.md
+++ b/steering_docs/dotnet-tech/wrapper.md
@@ -41,94 +41,86 @@ dotnetv4/{Service}/Actions/
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-///
-/// Purpose
-///
-/// Shows how to use the AWS SDK for .NET with {AWS Service} to
-/// {service description and main use cases}.
-///
-
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Amazon.{Service};
using Amazon.{Service}.Model;
-using Microsoft.Extensions.Logging;
+using Amazon.{ServiceDataAPI};
+using Amazon.{ServiceDataAPI}.Model;
-namespace Amazon.DocSamples.{Service}
+namespace {Service}Actions;
+
+// snippet-start:[{Service}.dotnetv4.{Service}Wrapper]
+///
+/// Wrapper class for Amazon {Service} operations.
+///
+public class {Service}Wrapper
{
- // snippet-start:[dotnetv4.example_code.{service}.{Service}Wrapper]
+ private readonly Amazon{Service}Client _{service}Client;
+ private readonly Amazon{ServiceDataAPI}Client _{service}DataClient;
+
///
- /// Encapsulates {AWS Service} functionality.
+ /// Constructor for {Service}Wrapper.
///
- public class {Service}Wrapper
+ /// Amazon {Service} client.
+ /// Amazon {Service} Data API client.
+ public {Service}Wrapper(Amazon{Service}Client {service}Client, Amazon{ServiceDataAPI}Client {service}DataClient)
{
- private readonly IAmazon{Service} _{service}Client;
- private readonly ILogger<{Service}Wrapper> _logger;
-
- ///
- /// Initializes a new instance of the {Service}Wrapper class.
- ///
- /// The {AWS Service} client.
- /// The logger instance.
- public {Service}Wrapper(IAmazon{Service} {service}Client, ILogger<{Service}Wrapper> logger)
- {
- _{service}Client = {service}Client;
- _logger = logger;
- }
-
+ _{service}Client = {service}Client;
+ _{service}DataClient = {service}DataClient;
}
- // snippet-end:[dotnetv4.example_code.{service}.{Service}Wrapper]
// Individual action methods follow...
}
+// snippet-end:[{Service}.dotnetv4.{Service}Wrapper]
+```
+
+**Key Changes:**
+- ✅ Use file-scoped namespaces
+- ✅ Use concrete client types (not interfaces) for simpler construction
+- ✅ No logger dependency (use Console.WriteLine for output)
+- ✅ Include both service client and data API client if needed
```
## Action Method Pattern
```csharp
- // snippet-start:[dotnetv4.example_code.{service}.{ActionName}]
- ///
- /// {Action description}.
- ///
- /// Parameter description.
- /// Response description.
- public async Task<{ActionName}Response> {ActionMethod}Async(string param)
+ // snippet-start:[{Service}.dotnetv4.{ActionName}]
+ ///
+ /// {Action description}.
+ ///
+ /// Parameter description.
+ /// Response description.
+ public async Task<{ReturnType}> {ActionMethod}Async(string param)
+ {
+ try
{
- try
- {
- var request = new {ActionName}Request
- {
- Parameter = param
- };
-
- var response = await _{service}Client.{ActionName}Async(request);
- _logger.LogInformation("{Action} completed successfully", "{ActionName}");
- return response;
- }
- catch (Amazon{Service}Exception ex)
+ var request = new {ActionName}Request
{
- var errorCode = ex.ErrorCode;
- if (errorCode == "SpecificError")
- {
- _logger.LogError("Specific error handling message");
- }
- else if (errorCode == "AnotherSpecificError")
- {
- _logger.LogError("Another specific error handling message");
- }
- else
- {
- _logger.LogError("Error in {ActionName}: {Message}", ex.Message);
- }
- throw;
- }
+ Parameter = param
+ };
+
+ var response = await _{service}Client.{ActionName}Async(request);
+ Console.WriteLine($"{Action} completed successfully");
+ return response.{Property};
}
- // snippet-end:[dotnetv4.example_code.{service}.{ActionName}]
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error in {ActionName}: {ex.Message}");
+ throw;
+ }
+ }
+ // snippet-end:[{Service}.dotnetv4.{ActionName}]
```
+**Key Changes:**
+- ✅ Use Console.WriteLine instead of logger
+- ✅ Simplified error handling (catch Exception instead of service-specific)
+- ✅ Return specific types instead of full response objects when appropriate
+
## Paginator Pattern for List Operations
```csharp
- // snippet-start:[dotnetv4.example_code.{service}.List{Resources}]
+ // snippet-start:[{Service}.dotnetv4.List{Resources}]
///
/// Lists all {resources} using pagination to retrieve complete results.
///
@@ -162,12 +154,12 @@ namespace Amazon.DocSamples.{Service}
throw;
}
}
- // snippet-end:[dotnetv4.example_code.{service}.List{Resources}]
+ // snippet-end:[{Service}.dotnetv4.List{Resources}]
```
## Paginator with Parameters Pattern
```csharp
- // snippet-start:[dotnetv4.example_code.{service}.List{Resources}WithFilter]
+ // snippet-start:[{Service}.dotnetv4.List{Resources}WithFilter]
///
/// Lists {resources} with optional filtering, using pagination.
///
@@ -200,7 +192,7 @@ namespace Amazon.DocSamples.{Service}
throw;
}
}
- // snippet-end:[dotnetv4.example_code.{service}.List{Resources}WithFilter]
+ // snippet-end:[{Service}.dotnetv4.List{Resources}WithFilter]
```
## Error Handling Requirements
@@ -290,4 +282,25 @@ await foreach (var response in itemsPaginator.Responses)
- Document return values with XML tags and descriptions
- Include usage examples in XML documentation where helpful
- Use proper snippet tags for documentation generation
-- Document pagination behavior in list method XML documentation
\ No newline at end of file
+- Document pagination behavior in list method XML documentation
+
+## Snippet Tag Format
+**CRITICAL**: Use the correct snippet tag format for all code examples:
+
+```csharp
+// ✅ CORRECT - Service name first, then dotnetv4
+// snippet-start:[Redshift.dotnetv4.CreateCluster]
+public async Task CreateClusterAsync(...)
+{
+ // Implementation
+}
+// snippet-end:[Redshift.dotnetv4.CreateCluster]
+
+// ❌ WRONG - Old format
+// snippet-start:[dotnetv4.example_code.redshift.CreateCluster]
+```
+
+**Format**: `[{Service}.dotnetv4.{ActionName}]`
+- Service name in PascalCase (e.g., Redshift, S3, DynamoDB)
+- Followed by `.dotnetv4.`
+- Action name in PascalCase (e.g., CreateCluster, ListBuckets, Hello)
\ No newline at end of file
From 85b9c86f271855a0fa53faa60051e7564acae52f Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Mon, 24 Nov 2025 13:21:01 -0600
Subject: [PATCH 02/16] Updates to scenario.
---
dotnetv4/Redshift/Actions/RedshiftWrapper.cs | 37 ++++--
dotnetv4/Redshift/Scenarios/RedshiftBasics.cs | 116 ++++--------------
.../Tests/RedshiftIntegrationTests.cs | 5 +-
3 files changed, 53 insertions(+), 105 deletions(-)
diff --git a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
index e557ed1ffa8..ff0a404ef94 100644
--- a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
+++ b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
@@ -42,7 +42,7 @@ public RedshiftWrapper(AmazonRedshiftClient redshiftClient, AmazonRedshiftDataAP
/// The node type for the cluster.
/// The cluster that was created.
public async Task CreateClusterAsync(string clusterIdentifier, string databaseName,
- string masterUsername, string masterUserPassword, string nodeType = "dc2.large")
+ string masterUsername, string masterUserPassword, string nodeType = "ra3.large")
{
try
{
@@ -159,15 +159,17 @@ public async Task DeleteClusterAsync(string clusterIdentifier)
///
/// The cluster identifier.
/// The database user.
+ /// The database name for authentication.
/// A list of database names.
- public async Task> ListDatabasesAsync(string clusterIdentifier, string dbUser)
+ public async Task> ListDatabasesAsync(string clusterIdentifier, string dbUser, string databaseName)
{
try
{
var request = new ListDatabasesRequest
{
ClusterIdentifier = clusterIdentifier,
- DbUser = dbUser
+ DbUser = dbUser,
+ Database = databaseName
};
var response = await _redshiftDataClient.ListDatabasesAsync(request);
@@ -231,7 +233,7 @@ year INTEGER NOT NULL
// snippet-start:[Redshift.dotnetv4.Insert]
///
- /// Insert a record into the Movies table.
+ /// Insert a record into the Movies table using parameterized query.
///
/// The cluster identifier.
/// The database name.
@@ -245,14 +247,20 @@ public async Task InsertMovieAsync(string clusterIdentifier, string data
{
try
{
- var sqlStatement = $"INSERT INTO Movies (id, title, year) VALUES ({id}, '{title}', {year})";
+ var sqlStatement = "INSERT INTO Movies (id, title, year) VALUES (:id, :title, :year)";
var request = new ExecuteStatementRequest
{
ClusterIdentifier = clusterIdentifier,
Database = database,
DbUser = dbUser,
- Sql = sqlStatement
+ Sql = sqlStatement,
+ Parameters = new List
+ {
+ new SqlParameter { Name = "id", Value = id.ToString() },
+ new SqlParameter { Name = "title", Value = title },
+ new SqlParameter { Name = "year", Value = year.ToString() }
+ }
};
var response = await _redshiftDataClient.ExecuteStatementAsync(request);
@@ -270,7 +278,7 @@ public async Task InsertMovieAsync(string clusterIdentifier, string data
// snippet-start:[Redshift.dotnetv4.Query]
///
- /// Query movies by year.
+ /// Query movies by year using parameterized query.
///
/// The cluster identifier.
/// The database name.
@@ -282,14 +290,18 @@ public async Task> QueryMoviesByYearAsync(string clusterIdentifier,
{
try
{
- var sqlStatement = $"SELECT title FROM Movies WHERE year = {year}";
+ var sqlStatement = "SELECT title FROM Movies WHERE year = :year";
var request = new ExecuteStatementRequest
{
ClusterIdentifier = clusterIdentifier,
Database = database,
DbUser = dbUser,
- Sql = sqlStatement
+ Sql = sqlStatement,
+ Parameters = new List
+ {
+ new SqlParameter { Name = "year", Value = year.ToString() }
+ }
};
var response = await _redshiftDataClient.ExecuteStatementAsync(request);
@@ -380,11 +392,12 @@ public async Task>> GetStatementResultAsync(string statementId)
private async Task WaitForStatementToCompleteAsync(string statementId)
{
var status = StatusString.SUBMITTED;
+ DescribeStatementResponse? response = null;
while (status == StatusString.SUBMITTED || status == StatusString.PICKED || status == StatusString.STARTED)
{
await Task.Delay(1000); // Wait 1 second
- var response = await DescribeStatementAsync(statementId);
+ response = await DescribeStatementAsync(statementId);
status = response.Status;
Console.WriteLine($"...{status}");
}
@@ -395,8 +408,10 @@ private async Task WaitForStatementToCompleteAsync(string statementId)
}
else
{
+ var errorMessage = response?.Error ?? "Unknown error";
Console.WriteLine($"The statement failed with status: {status}");
- throw new Exception($"Statement execution failed with status: {status}");
+ Console.WriteLine($"Error message: {errorMessage}");
+ throw new Exception($"Statement execution failed with status: {status}. Error: {errorMessage}");
}
}
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
index d6fb9c82a49..bc416072d0d 100644
--- a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
@@ -19,7 +19,7 @@ namespace RedshiftBasics;
public class RedshiftBasics
{
private static RedshiftWrapper? _redshiftWrapper;
- private static readonly string MoviesFilePath = "../../../../../../../resources/sample_files/movies.json";
+ private static readonly string _moviesFilePath = "../../../../../../resources/sample_files/movies.json";
///
/// Main method for the Amazon Redshift Getting Started scenario.
@@ -32,16 +32,16 @@ public static async Task Main(string[] args)
var redshiftDataClient = new AmazonRedshiftDataAPIServiceClient();
_redshiftWrapper = new RedshiftWrapper(redshiftClient, redshiftDataClient);
- Console.WriteLine("================================================================================");
+ Console.WriteLine(
+ "================================================================================");
Console.WriteLine("Welcome to the Amazon Redshift SDK Getting Started scenario.");
- Console.WriteLine("This .NET program demonstrates how to interact with Amazon Redshift by using the AWS SDK for .NET.");
- Console.WriteLine("Amazon Redshift is a fully managed, petabyte-scale data warehouse service hosted in the cloud.");
- Console.WriteLine("The program's primary functionality includes cluster creation, verification of cluster readiness,");
- Console.WriteLine("list databases, table creation, data population within the table, and execution of SQL statements.");
- Console.WriteLine("Furthermore, it demonstrates the process of querying data from the Movie table.");
- Console.WriteLine("Upon completion of the program, all AWS resources are cleaned up.");
+ Console.WriteLine(
+ "This .NET program demonstrates how to interact with Amazon Redshift by using the AWS SDK for .NET.");
+ Console.WriteLine(
+ "Upon completion of the program, all resources are cleaned up.");
Console.WriteLine("Let's get started...");
- Console.WriteLine("================================================================================");
+ Console.WriteLine(
+ "================================================================================");
try
{
@@ -65,6 +65,9 @@ public static async Task Main(string[] args)
private static async Task RunScenarioAsync()
{
// Step 1: Get user credentials
+ if (!File.Exists(_moviesFilePath))
+ Console.WriteLine("filenotfound");
+
Console.WriteLine("Please enter your user name (default is awsuser):");
var userName = Console.ReadLine();
if (string.IsNullOrEmpty(userName))
@@ -76,7 +79,6 @@ private static async Task RunScenarioAsync()
if (string.IsNullOrEmpty(userPassword))
userPassword = "AwsUser1000";
- Console.WriteLine("================================================================================");
Console.WriteLine("================================================================================");
Console.WriteLine("A Redshift cluster refers to the collection of computing resources and storage that work together to process and analyze large volumes of data.");
@@ -112,7 +114,7 @@ private static async Task RunScenarioAsync()
Console.WriteLine($"List databases in {clusterIdentifier}");
Console.WriteLine("Press Enter to continue...");
Console.ReadLine();
- await _redshiftWrapper.ListDatabasesAsync(clusterIdentifier, userName);
+ await _redshiftWrapper.ListDatabasesAsync(clusterIdentifier, userName, databaseName);
Console.WriteLine("================================================================================");
// Step 6: Create Movies table
@@ -204,99 +206,29 @@ private static async Task RunScenarioAsync()
/// Number of records to insert.
private static async Task PopulateMoviesTableAsync(string clusterIdentifier, string database, string dbUser, int recordCount)
{
- try
- {
- if (!File.Exists(MoviesFilePath))
- {
- Console.WriteLine($"Movies file not found at {MoviesFilePath}. Using sample data instead.");
- await PopulateWithSampleDataAsync(clusterIdentifier, database, dbUser, recordCount);
- return;
- }
-
- var jsonContent = await File.ReadAllTextAsync(MoviesFilePath);
- var movies = JsonSerializer.Deserialize>(jsonContent);
-
- if (movies == null)
- {
- Console.WriteLine("Failed to parse movies JSON file. Using sample data instead.");
- await PopulateWithSampleDataAsync(clusterIdentifier, database, dbUser, recordCount);
- return;
- }
-
- var insertCount = Math.Min(recordCount, movies.Count);
-
- for (int i = 0; i < insertCount; i++)
- {
- var movie = movies[i];
- await _redshiftWrapper!.InsertMovieAsync(clusterIdentifier, database, dbUser, movie.Id, movie.Title, movie.Year);
- }
- }
- catch (Exception ex)
+ if (!File.Exists(_moviesFilePath))
{
- Console.WriteLine($"Error populating movies table: {ex.Message}");
- Console.WriteLine("Using sample data instead.");
- await PopulateWithSampleDataAsync(clusterIdentifier, database, dbUser, recordCount);
+ throw new FileNotFoundException($"Required movies data file not found at: {_moviesFilePath}");
}
- }
- ///
- /// Populate the table with sample movie data when JSON file is not available.
- ///
- /// The cluster identifier.
- /// The database name.
- /// The database user.
- /// Number of records to insert.
- private static async Task PopulateWithSampleDataAsync(string clusterIdentifier, string database, string dbUser, int recordCount)
- {
- var sampleMovies = new List
+ var jsonContent = await File.ReadAllTextAsync(_moviesFilePath);
+ var options = new JsonSerializerOptions
{
- new Movie { Id = 1, Title = "Rush", Year = 2013 },
- new Movie { Id = 2, Title = "Prisoners", Year = 2013 },
- new Movie { Id = 3, Title = "The Hunger Games: Catching Fire", Year = 2013 },
- new Movie { Id = 4, Title = "Thor: The Dark World", Year = 2013 },
- new Movie { Id = 5, Title = "This Is the End", Year = 2013 },
- new Movie { Id = 6, Title = "Despicable Me 2", Year = 2013 },
- new Movie { Id = 7, Title = "Man of Steel", Year = 2013 },
- new Movie { Id = 8, Title = "Gravity", Year = 2013 },
- new Movie { Id = 9, Title = "Pacific Rim", Year = 2013 },
- new Movie { Id = 10, Title = "World War Z", Year = 2013 },
- new Movie { Id = 11, Title = "Iron Man 3", Year = 2013 },
- new Movie { Id = 12, Title = "Star Trek Into Darkness", Year = 2013 },
- new Movie { Id = 13, Title = "Fast & Furious 6", Year = 2013 },
- new Movie { Id = 14, Title = "Monsters University", Year = 2013 },
- new Movie { Id = 15, Title = "Elysium", Year = 2013 },
- new Movie { Id = 16, Title = "The Hobbit: The Desolation of Smaug", Year = 2013 },
- new Movie { Id = 17, Title = "Captain Phillips", Year = 2013 },
- new Movie { Id = 18, Title = "Ender's Game", Year = 2013 },
- new Movie { Id = 19, Title = "The Wolverine", Year = 2013 },
- new Movie { Id = 20, Title = "Now You See Me", Year = 2013 }
+ PropertyNameCaseInsensitive = true
};
+ var movies = JsonSerializer.Deserialize>(jsonContent, options);
- // Generate more movies if needed
- var movieList = new List(sampleMovies);
- var random = new Random();
- var years = new[] { 2012, 2013, 2014 };
- var baseMovieTitles = new[] { "Action Movie", "Drama Film", "Comedy Show", "Thriller Movie", "Adventure Film", "Sci-Fi Movie", "Horror Film" };
-
- while (movieList.Count < recordCount)
+ if (movies == null || movies.Count == 0)
{
- var baseTitle = baseMovieTitles[random.Next(baseMovieTitles.Length)];
- var year = years[random.Next(years.Length)];
- var movie = new Movie
- {
- Id = movieList.Count + 1,
- Title = $"{baseTitle} {movieList.Count + 1}",
- Year = year
- };
- movieList.Add(movie);
+ throw new InvalidOperationException("Failed to parse movies JSON file or file is empty.");
}
- var insertCount = Math.Min(recordCount, movieList.Count);
+ var insertCount = Math.Min(recordCount, movies.Count);
for (int i = 0; i < insertCount; i++)
{
- var movie = movieList[i];
- await _redshiftWrapper!.InsertMovieAsync(clusterIdentifier, database, dbUser, movie.Id, movie.Title, movie.Year);
+ var movie = movies[i];
+ await _redshiftWrapper!.InsertMovieAsync(clusterIdentifier, database, dbUser, i, movie.Title, movie.Year);
}
}
diff --git a/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
index f10e0b4cbcd..77632c3439c 100644
--- a/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
+++ b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
@@ -99,7 +99,7 @@ public async Task RedshiftFullWorkflow_Integration_CompletesSuccessfully()
// Step 3: List databases
Console.WriteLine("Listing databases...");
- var databases = await _redshiftWrapper.ListDatabasesAsync(_testClusterIdentifier!, TestUsername);
+ var databases = await _redshiftWrapper.ListDatabasesAsync(_testClusterIdentifier!, TestUsername, TestDatabaseName);
Assert.IsNotNull(databases);
Assert.IsTrue(databases.Count > 0);
Console.WriteLine($"Found {databases.Count} databases.");
@@ -343,7 +343,8 @@ public async Task ListDatabases_WithExistingCluster_ReturnsResults()
{
var databases = await _redshiftWrapper.ListDatabasesAsync(
availableCluster.ClusterIdentifier,
- TestUsername);
+ TestUsername,
+ TestDatabaseName);
Assert.IsNotNull(databases);
Console.WriteLine($"Found {databases.Count} databases in cluster {availableCluster.ClusterIdentifier}");
From afe5c311dbe322395cf0edad02d812277eed4103 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Mon, 24 Nov 2025 14:06:39 -0600
Subject: [PATCH 03/16] Updates to wrapper and specification.
---
dotnetv4/Redshift/Actions/RedshiftWrapper.cs | 71 +++++++++++++++++---
scenarios/basics/redshift/SPECIFICATION.md | 29 ++++++++
2 files changed, 89 insertions(+), 11 deletions(-)
diff --git a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
index ff0a404ef94..260004dd4fc 100644
--- a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
+++ b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
@@ -61,9 +61,14 @@ public async Task CreateClusterAsync(string clusterIdentifier, string d
Console.WriteLine($"Created cluster {clusterIdentifier}");
return response.Cluster;
}
+ catch (ClusterAlreadyExistsException ex)
+ {
+ Console.WriteLine($"Cluster already exists: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error creating cluster: {ex.Message}");
+ Console.WriteLine($"Couldn't create cluster. Here's why: {ex.Message}");
throw;
}
}
@@ -88,9 +93,14 @@ public async Task> DescribeClustersAsync(string? clusterIdentifier
var response = await _redshiftClient.DescribeClustersAsync(request);
return response.Clusters;
}
+ catch (ClusterNotFoundException ex)
+ {
+ Console.WriteLine($"Cluster not found: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error describing clusters: {ex.Message}");
+ Console.WriteLine($"Couldn't describe clusters. Here's why: {ex.Message}");
throw;
}
}
@@ -117,9 +127,14 @@ public async Task ModifyClusterAsync(string clusterIdentifier, string p
Console.WriteLine($"The modified cluster was successfully modified and has {preferredMaintenanceWindow} as the maintenance window");
return response.Cluster;
}
+ catch (ClusterNotFoundException ex)
+ {
+ Console.WriteLine($"Cluster not found: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error modifying cluster: {ex.Message}");
+ Console.WriteLine($"Couldn't modify cluster. Here's why: {ex.Message}");
throw;
}
}
@@ -145,9 +160,14 @@ public async Task DeleteClusterAsync(string clusterIdentifier)
Console.WriteLine($"The {clusterIdentifier} was deleted");
return response.Cluster;
}
+ catch (ClusterNotFoundException ex)
+ {
+ Console.WriteLine($"Cluster not found: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error deleting cluster: {ex.Message}");
+ Console.WriteLine($"Couldn't delete cluster. Here's why: {ex.Message}");
throw;
}
}
@@ -183,9 +203,14 @@ public async Task> ListDatabasesAsync(string clusterIdentifier, str
return databases;
}
+ catch (Amazon.RedshiftDataAPIService.Model.ValidationException ex)
+ {
+ Console.WriteLine($"Validation error: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error listing databases: {ex.Message}");
+ Console.WriteLine($"Couldn't list databases. Here's why: {ex.Message}");
throw;
}
}
@@ -223,9 +248,14 @@ year INTEGER NOT NULL
Console.WriteLine("Table created: Movies");
return response.Id;
}
+ catch (Amazon.RedshiftDataAPIService.Model.ValidationException ex)
+ {
+ Console.WriteLine($"Validation error: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error creating table: {ex.Message}");
+ Console.WriteLine($"Couldn't create table. Here's why: {ex.Message}");
throw;
}
}
@@ -268,9 +298,14 @@ public async Task InsertMovieAsync(string clusterIdentifier, string data
Console.WriteLine($"Inserted: {title} ({year})");
return response.Id;
}
+ catch (Amazon.RedshiftDataAPIService.Model.ValidationException ex)
+ {
+ Console.WriteLine($"Validation error: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error inserting movie: {ex.Message}");
+ Console.WriteLine($"Couldn't insert movie. Here's why: {ex.Message}");
throw;
}
}
@@ -324,9 +359,14 @@ public async Task> QueryMoviesByYearAsync(string clusterIdentifier,
return movieTitles;
}
+ catch (Amazon.RedshiftDataAPIService.Model.ValidationException ex)
+ {
+ Console.WriteLine($"Validation error: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error querying movies: {ex.Message}");
+ Console.WriteLine($"Couldn't query movies. Here's why: {ex.Message}");
throw;
}
}
@@ -350,9 +390,14 @@ public async Task DescribeStatementAsync(string state
var response = await _redshiftDataClient.DescribeStatementAsync(request);
return response;
}
+ catch (Amazon.RedshiftDataAPIService.Model.ResourceNotFoundException ex)
+ {
+ Console.WriteLine($"Statement not found: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error describing statement: {ex.Message}");
+ Console.WriteLine($"Couldn't describe statement. Here's why: {ex.Message}");
throw;
}
}
@@ -376,9 +421,14 @@ public async Task>> GetStatementResultAsync(string statementId)
var response = await _redshiftDataClient.GetStatementResultAsync(request);
return response.Records;
}
+ catch (Amazon.RedshiftDataAPIService.Model.ResourceNotFoundException ex)
+ {
+ Console.WriteLine($"Statement not found: {ex.Message}");
+ throw;
+ }
catch (Exception ex)
{
- Console.WriteLine($"Error getting statement result: {ex.Message}");
+ Console.WriteLine($"Couldn't get statement result. Here's why: {ex.Message}");
throw;
}
}
@@ -411,7 +461,6 @@ private async Task WaitForStatementToCompleteAsync(string statementId)
var errorMessage = response?.Error ?? "Unknown error";
Console.WriteLine($"The statement failed with status: {status}");
Console.WriteLine($"Error message: {errorMessage}");
- throw new Exception($"Statement execution failed with status: {status}. Error: {errorMessage}");
}
}
diff --git a/scenarios/basics/redshift/SPECIFICATION.md b/scenarios/basics/redshift/SPECIFICATION.md
index b518011e2e8..086326c8efe 100644
--- a/scenarios/basics/redshift/SPECIFICATION.md
+++ b/scenarios/basics/redshift/SPECIFICATION.md
@@ -13,6 +13,20 @@ The following user input is required for this SDK getting started scenario:
- The year to use to query records from the database.
- Whether or not to delete the Amazon Redshift cluster.
+## SQL Statement Requirements
+
+All SQL statements that include user input or variable data MUST use parameterized queries to prevent SQL injection vulnerabilities. This applies to:
+
+- INSERT statements when adding movie records (use parameters for id, title, and year values)
+- SELECT statements when querying by year (use parameters for the year value)
+- Any other SQL operations that incorporate dynamic values
+
+Example of parameterized query usage:
+- Instead of: `SELECT * FROM Movies WHERE year = 2013`
+- Use: `SELECT * FROM Movies WHERE year = :year` with a parameter binding for `:year`
+
+This security best practice ensures that user input is properly escaped and prevents malicious SQL code injection.
+
## Hello Redshift
This program is intended for users not familiar with the Redshift SDK to easily get up an running. The logic is to show use of `redshiftClient.describeClustersPaginator()`.
@@ -148,6 +162,21 @@ This concludes the Amazon Redshift SDK Getting Started scenario.
```
+## Exception Handling
+
+The following table lists the exceptions that should be caught and handled for each action in the scenario:
+
+| Action | Exception | Handling |
+|---------------------------|--------------------------------|---------------------------------------------------------------------------------------------|
+| `createCluster` | ClusterAlreadyExistsFault | Notify the user that a cluster with this identifier already exists and exit. |
+| `describeClusters` | ClusterNotFoundFault | Notify the user that the specified cluster was not found. |
+| `listDatabases` | ValidationException | Notify the user that the cluster is not available or parameters are invalid. |
+| `executeStatement` | ValidationException | Notify the user of SQL syntax errors or invalid query parameters. |
+| `describeStatement` | ResourceNotFoundException | Notify the user that the statement ID was not found. |
+| `getStatementResult` | ResourceNotFoundException | Notify the user that results are not available for the statement ID. |
+| `modifyCluster` | ClusterNotFoundFault | Notify the user that the cluster to modify was not found. |
+| `deleteCluster` | ClusterNotFoundFault | Notify the user that the cluster to delete was not found. |
+
## SOS Tags
The following table describes the metadata used in this SDK Getting Started Scenario.
From 264596b91408ba6fbb9f30b965bc7d5a8726fb81 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Mon, 24 Nov 2025 14:56:07 -0600
Subject: [PATCH 04/16] Updates to interactivity and setup.
---
dotnetv4/Redshift/Actions/RedshiftWrapper.cs | 16 +-
dotnetv4/Redshift/Scenarios/RedshiftBasics.cs | 245 ++++++++++--------
.../Redshift/Scenarios/RedshiftBasics.csproj | 3 +
3 files changed, 153 insertions(+), 111 deletions(-)
diff --git a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
index 260004dd4fc..67d7d436f97 100644
--- a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
+++ b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
@@ -17,15 +17,15 @@ namespace RedshiftActions;
///
public class RedshiftWrapper
{
- private readonly AmazonRedshiftClient _redshiftClient;
- private readonly AmazonRedshiftDataAPIServiceClient _redshiftDataClient;
+ private readonly IAmazonRedshift _redshiftClient;
+ private readonly IAmazonRedshiftDataAPIService _redshiftDataClient;
///
/// Constructor for RedshiftWrapper.
///
/// Amazon Redshift client.
/// Amazon Redshift Data API client.
- public RedshiftWrapper(AmazonRedshiftClient redshiftClient, AmazonRedshiftDataAPIServiceClient redshiftDataClient)
+ public RedshiftWrapper(IAmazonRedshift redshiftClient, IAmazonRedshiftDataAPIService redshiftDataClient)
{
_redshiftClient = redshiftClient;
_redshiftDataClient = redshiftDataClient;
@@ -468,12 +468,16 @@ private async Task WaitForStatementToCompleteAsync(string statementId)
/// Wait for a cluster to become available.
///
/// The cluster identifier.
+ /// Whether to prompt for user input.
/// A task representing the asynchronous operation.
- public async Task WaitForClusterAvailableAsync(string clusterIdentifier)
+ public async Task WaitForClusterAvailableAsync(string clusterIdentifier, bool isInteractive = true)
{
Console.WriteLine($"Wait until {clusterIdentifier} is available.");
- Console.WriteLine("Press Enter to continue...");
- Console.ReadLine();
+ if (isInteractive)
+ {
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ }
Console.WriteLine("Waiting for cluster to become available. This may take a few minutes.");
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
index bc416072d0d..fd51cca0496 100644
--- a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
@@ -8,6 +8,8 @@
using System.Threading.Tasks;
using Amazon.Redshift;
using Amazon.RedshiftDataAPIService;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
using RedshiftActions;
namespace RedshiftBasics;
@@ -18,7 +20,8 @@ namespace RedshiftBasics;
///
public class RedshiftBasics
{
- private static RedshiftWrapper? _redshiftWrapper;
+ public static bool IsInteractive = true;
+ public static RedshiftWrapper? Wrapper = null;
private static readonly string _moviesFilePath = "../../../../../../resources/sample_files/movies.json";
///
@@ -27,78 +30,80 @@ public class RedshiftBasics
/// Command line arguments.
public static async Task Main(string[] args)
{
- // Initialize the Amazon Redshift clients
- var redshiftClient = new AmazonRedshiftClient();
- var redshiftDataClient = new AmazonRedshiftDataAPIServiceClient();
- _redshiftWrapper = new RedshiftWrapper(redshiftClient, redshiftDataClient);
-
- Console.WriteLine(
- "================================================================================");
- Console.WriteLine("Welcome to the Amazon Redshift SDK Getting Started scenario.");
- Console.WriteLine(
- "This .NET program demonstrates how to interact with Amazon Redshift by using the AWS SDK for .NET.");
- Console.WriteLine(
- "Upon completion of the program, all resources are cleaned up.");
- Console.WriteLine("Let's get started...");
- Console.WriteLine(
- "================================================================================");
+ using var host = Host.CreateDefaultBuilder(args)
+ .ConfigureServices((_, services) =>
+ services.AddAWSService()
+ .AddAWSService()
+ .AddTransient()
+ )
+ .Build();
- try
- {
- await RunScenarioAsync();
- }
- catch (Exception ex)
- {
- Console.WriteLine($"An error occurred: {ex.Message}");
- Console.WriteLine(ex.StackTrace);
- }
- finally
- {
- redshiftClient.Dispose();
- redshiftDataClient.Dispose();
- }
+ Wrapper = host.Services.GetRequiredService();
+
+ await RunScenarioAsync();
}
///
/// Run the complete Amazon Redshift scenario.
///
- private static async Task RunScenarioAsync()
+ public static async Task RunScenarioAsync()
{
- // Step 1: Get user credentials
- if (!File.Exists(_moviesFilePath))
- Console.WriteLine("filenotfound");
-
- Console.WriteLine("Please enter your user name (default is awsuser):");
- var userName = Console.ReadLine();
- if (string.IsNullOrEmpty(userName))
- userName = "awsuser";
-
- Console.WriteLine("================================================================================");
- Console.WriteLine("Please enter your user password (default is AwsUser1000):");
- var userPassword = Console.ReadLine();
- if (string.IsNullOrEmpty(userPassword))
- userPassword = "AwsUser1000";
-
- Console.WriteLine("================================================================================");
- Console.WriteLine("A Redshift cluster refers to the collection of computing resources and storage that work together to process and analyze large volumes of data.");
-
- // Step 2: Get cluster identifier
- Console.WriteLine("Enter a cluster id value (default is redshift-cluster-movies):");
- var clusterIdentifier = Console.ReadLine();
- if (string.IsNullOrEmpty(clusterIdentifier))
- clusterIdentifier = "redshift-cluster-movies";
-
- var databaseName = "dev";
-
try
{
+ Console.WriteLine(
+ "================================================================================");
+ Console.WriteLine("Welcome to the Amazon Redshift SDK Getting Started scenario.");
+ Console.WriteLine(
+ "This .NET program demonstrates how to interact with Amazon Redshift by using the AWS SDK for .NET.");
+ Console.WriteLine(
+ "Upon completion of the program, all resources are cleaned up.");
+ Console.WriteLine("Let's get started...");
+ Console.WriteLine(
+ "================================================================================");
+
+ // Set all variables to default values
+ string userName = "awsuser";
+ string userPassword = "AwsUser1000";
+ string clusterIdentifier = "redshift-cluster-movies";
+ var databaseName = "dev";
+ int recordCount = 50;
+ int year = 2013;
+
+ // Step 1: Get user credentials (if interactive)
+ if (IsInteractive)
+ {
+ Console.WriteLine("Please enter your user name (default is awsuser):");
+ var userInput = Console.ReadLine();
+ if (!string.IsNullOrEmpty(userInput))
+ userName = userInput;
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("Please enter your user password (default is AwsUser1000):");
+ var passwordInput = Console.ReadLine();
+ if (!string.IsNullOrEmpty(passwordInput))
+ userPassword = passwordInput;
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("A Redshift cluster refers to the collection of computing resources and storage that work together to process and analyze large volumes of data.");
+
+ // Step 2: Get cluster identifier
+ Console.WriteLine("Enter a cluster id value (default is redshift-cluster-movies):");
+ var clusterInput = Console.ReadLine();
+ if (!string.IsNullOrEmpty(clusterInput))
+ clusterIdentifier = clusterInput;
+ }
+ else
+ {
+ Console.WriteLine($"Using default values: userName={userName}, clusterIdentifier={clusterIdentifier}");
+ }
+
// Step 3: Create Redshift cluster
- await _redshiftWrapper!.CreateClusterAsync(clusterIdentifier, databaseName, userName, userPassword);
+ await Wrapper!.CreateClusterAsync(clusterIdentifier, databaseName, userName, userPassword);
Console.WriteLine("================================================================================");
// Step 4: Wait for cluster to become available
Console.WriteLine("================================================================================");
- await _redshiftWrapper.WaitForClusterAvailableAsync(clusterIdentifier);
+ await Wrapper.WaitForClusterAvailableAsync(clusterIdentifier, IsInteractive);
Console.WriteLine("================================================================================");
// Step 5: List databases
@@ -106,37 +111,57 @@ private static async Task RunScenarioAsync()
Console.WriteLine($" When you created {clusterIdentifier}, the dev database is created by default and used in this scenario.");
Console.WriteLine(" To create a custom database, you need to have a CREATEDB privilege.");
Console.WriteLine(" For more information, see the documentation here: https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_DATABASE.html.");
- Console.WriteLine("Press Enter to continue...");
- Console.ReadLine();
+ if (IsInteractive)
+ {
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ }
Console.WriteLine("================================================================================");
Console.WriteLine("================================================================================");
Console.WriteLine($"List databases in {clusterIdentifier}");
- Console.WriteLine("Press Enter to continue...");
- Console.ReadLine();
- await _redshiftWrapper.ListDatabasesAsync(clusterIdentifier, userName, databaseName);
+ if (IsInteractive)
+ {
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ }
+ await Wrapper.ListDatabasesAsync(clusterIdentifier, userName, databaseName);
Console.WriteLine("================================================================================");
// Step 6: Create Movies table
Console.WriteLine("================================================================================");
Console.WriteLine("Now you will create a table named Movies.");
- Console.WriteLine("Press Enter to continue...");
- Console.ReadLine();
- await _redshiftWrapper.CreateTableAsync(clusterIdentifier, databaseName, userName);
+ if (IsInteractive)
+ {
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ }
+ await Wrapper.CreateTableAsync(clusterIdentifier, databaseName, userName);
Console.WriteLine("================================================================================");
// Step 7: Populate the Movies table
Console.WriteLine("================================================================================");
Console.WriteLine("Populate the Movies table using the Movies.json file.");
- Console.WriteLine("Specify the number of records you would like to add to the Movies Table.");
- Console.WriteLine("Please enter a value between 50 and 200.");
- Console.Write("Enter a value: ");
-
- var recordCountInput = Console.ReadLine();
- if (!int.TryParse(recordCountInput, out var recordCount) || recordCount < 50 || recordCount > 200)
+
+ if (IsInteractive)
{
- recordCount = 50;
- Console.WriteLine($"Invalid input. Using default value of {recordCount}.");
+ Console.WriteLine("Specify the number of records you would like to add to the Movies Table.");
+ Console.WriteLine("Please enter a value between 50 and 200.");
+ Console.Write("Enter a value: ");
+
+ var recordCountInput = Console.ReadLine();
+ if (int.TryParse(recordCountInput, out var inputCount) && inputCount >= 50 && inputCount <= 200)
+ {
+ recordCount = inputCount;
+ }
+ else
+ {
+ Console.WriteLine($"Invalid input. Using default value of {recordCount}.");
+ }
+ }
+ else
+ {
+ Console.WriteLine($"Using default record count: {recordCount}");
}
await PopulateMoviesTableAsync(clusterIdentifier, databaseName, userName, recordCount);
@@ -146,55 +171,66 @@ private static async Task RunScenarioAsync()
// Step 8 & 9: Query movies by year
Console.WriteLine("================================================================================");
Console.WriteLine("Query the Movies table by year. Enter a value between 2012-2014.");
- Console.Write("Enter a year: ");
- var yearInput = Console.ReadLine();
- if (!int.TryParse(yearInput, out var year) || year < 2012 || year > 2014)
+
+ if (IsInteractive)
+ {
+ Console.Write("Enter a year: ");
+ var yearInput = Console.ReadLine();
+ if (int.TryParse(yearInput, out var inputYear) && inputYear >= 2012 && inputYear <= 2014)
+ {
+ year = inputYear;
+ }
+ else
+ {
+ Console.WriteLine($"Invalid input. Using default value of {year}.");
+ }
+ }
+ else
{
- year = 2013;
- Console.WriteLine($"Invalid input. Using default value of {year}.");
+ Console.WriteLine($"Using default year: {year}");
}
- await _redshiftWrapper.QueryMoviesByYearAsync(clusterIdentifier, databaseName, userName, year);
+ await Wrapper.QueryMoviesByYearAsync(clusterIdentifier, databaseName, userName, year);
Console.WriteLine("================================================================================");
// Step 10: Modify the cluster
Console.WriteLine("================================================================================");
Console.WriteLine("Now you will modify the Redshift cluster.");
- Console.WriteLine("Press Enter to continue...");
- Console.ReadLine();
- await _redshiftWrapper.ModifyClusterAsync(clusterIdentifier, "wed:07:30-wed:08:00");
+ if (IsInteractive)
+ {
+ Console.WriteLine("Press Enter to continue...");
+ Console.ReadLine();
+ }
+ await Wrapper.ModifyClusterAsync(clusterIdentifier, "wed:07:30-wed:08:00");
Console.WriteLine("================================================================================");
// Step 11 & 12: Delete cluster confirmation
Console.WriteLine("================================================================================");
- Console.WriteLine("Would you like to delete the Amazon Redshift cluster? (y/n)");
- var deleteResponse = Console.ReadLine();
- if (deleteResponse?.ToLower() == "y" || deleteResponse?.ToLower() == "yes")
+ if (IsInteractive)
{
- await _redshiftWrapper.DeleteClusterAsync(clusterIdentifier);
+ Console.WriteLine("Would you like to delete the Amazon Redshift cluster? (y/n)");
+ var deleteResponse = Console.ReadLine();
+ if (deleteResponse?.ToLower() == "y" || deleteResponse?.ToLower() == "yes")
+ {
+ await Wrapper.DeleteClusterAsync(clusterIdentifier);
+ }
}
+ else
+ {
+ Console.WriteLine("Deleting the Amazon Redshift cluster (non-interactive mode)...");
+ await Wrapper.DeleteClusterAsync(clusterIdentifier);
+ }
+ Console.WriteLine("================================================================================");
+
+ Console.WriteLine("================================================================================");
+ Console.WriteLine("This concludes the Amazon Redshift SDK Getting Started scenario.");
Console.WriteLine("================================================================================");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred during the scenario: {ex.Message}");
-
- // Attempt cleanup
- Console.WriteLine("Attempting to clean up resources...");
- try
- {
- await _redshiftWrapper!.DeleteClusterAsync(clusterIdentifier);
- }
- catch (Exception cleanupEx)
- {
- Console.WriteLine($"Cleanup failed: {cleanupEx.Message}");
- }
throw;
}
-
- Console.WriteLine("================================================================================");
- Console.WriteLine("This concludes the Amazon Redshift SDK Getting Started scenario.");
- Console.WriteLine("================================================================================");
}
///
@@ -228,7 +264,7 @@ private static async Task PopulateMoviesTableAsync(string clusterIdentifier, str
for (int i = 0; i < insertCount; i++)
{
var movie = movies[i];
- await _redshiftWrapper!.InsertMovieAsync(clusterIdentifier, database, dbUser, i, movie.Title, movie.Year);
+ await Wrapper!.InsertMovieAsync(clusterIdentifier, database, dbUser, i, movie.Title, movie.Year);
}
}
@@ -237,7 +273,6 @@ private static async Task PopulateMoviesTableAsync(string clusterIdentifier, str
///
private class Movie
{
- public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public int Year { get; set; }
}
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj b/dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj
index bf152493dec..df3ebce01f5 100644
--- a/dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.csproj
@@ -9,8 +9,11 @@
+
+
+
From 4592f3692b2e257674b5be227535829d591195c5 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Mon, 24 Nov 2025 15:25:13 -0600
Subject: [PATCH 05/16] Add integration test.
---
dotnetv4/Redshift/Scenarios/RedshiftBasics.cs | 5 +
.../Tests/RedshiftIntegrationTests.cs | 378 ++----------------
dotnetv4/Redshift/Tests/RedshiftTests.csproj | 15 +-
3 files changed, 53 insertions(+), 345 deletions(-)
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
index fd51cca0496..332fc5ac883 100644
--- a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
@@ -10,6 +10,7 @@
using Amazon.RedshiftDataAPIService;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
using RedshiftActions;
namespace RedshiftBasics;
@@ -22,6 +23,7 @@ public class RedshiftBasics
{
public static bool IsInteractive = true;
public static RedshiftWrapper? Wrapper = null;
+ public static ILogger logger = null!;
private static readonly string _moviesFilePath = "../../../../../../resources/sample_files/movies.json";
///
@@ -38,6 +40,9 @@ public static async Task Main(string[] args)
)
.Build();
+ logger = LoggerFactory.Create(builder => { builder.AddConsole(); })
+ .CreateLogger();
+
Wrapper = host.Services.GetRequiredService();
await RunScenarioAsync();
diff --git a/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
index 77632c3439c..3d8da0e6216 100644
--- a/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
+++ b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
@@ -5,359 +5,57 @@
using System.Threading.Tasks;
using Amazon.Redshift;
using Amazon.RedshiftDataAPIService;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.Extensions.Logging;
+using Moq;
using RedshiftActions;
+using Xunit;
namespace RedshiftTests;
///
-/// Integration tests for Amazon Redshift operations.
-/// These tests require actual AWS credentials and will create real AWS resources.
+/// Integration tests for the AWS Redshift Basics scenario.
///
-[TestClass]
-public class RedshiftIntegrationTests
+public class RedshiftBasicsTests
{
- private static RedshiftWrapper? _redshiftWrapper;
- private static string? _testClusterIdentifier;
- private const string TestDatabaseName = "dev";
- private const string TestUsername = "testuser";
- private const string TestPassword = "TestPassword123!";
-
- [ClassInitialize]
- public static void ClassInitialize(TestContext context)
- {
- // Initialize clients
- var redshiftClient = new AmazonRedshiftClient();
- var redshiftDataClient = new AmazonRedshiftDataAPIServiceClient();
- _redshiftWrapper = new RedshiftWrapper(redshiftClient, redshiftDataClient);
-
- // Generate unique cluster identifier
- _testClusterIdentifier = $"test-cluster-{DateTime.Now:yyyyMMddHHmmss}";
-
- Console.WriteLine($"Integration tests will use cluster: {_testClusterIdentifier}");
- }
-
- [ClassCleanup]
- public static async Task ClassCleanup()
- {
- // Clean up any remaining test resources
- if (_redshiftWrapper != null && !string.IsNullOrEmpty(_testClusterIdentifier))
- {
- try
- {
- Console.WriteLine($"Cleaning up test cluster: {_testClusterIdentifier}");
- await _redshiftWrapper.DeleteClusterAsync(_testClusterIdentifier);
- Console.WriteLine("Test cluster cleanup initiated.");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Warning: Failed to cleanup test cluster: {ex.Message}");
- }
- }
- }
-
- [TestMethod]
- [TestCategory("Integration")]
- public async Task DescribeClusters_Integration_ReturnsClusterList()
- {
- // Act
- var clusters = await _redshiftWrapper!.DescribeClustersAsync();
-
- // Assert
- Assert.IsNotNull(clusters);
- // Note: We don't assert specific count since other clusters might exist
- Console.WriteLine($"Found {clusters.Count} existing clusters.");
- }
-
- [TestMethod]
- [TestCategory("Integration")]
- [TestCategory("LongRunning")]
- public async Task RedshiftFullWorkflow_Integration_CompletesSuccessfully()
- {
- // This test runs the complete Redshift workflow
- // Note: This test can take 10-15 minutes to complete due to cluster creation time
-
- try
- {
- Console.WriteLine("Starting Redshift full workflow integration test...");
-
- // Step 1: Create cluster
- Console.WriteLine($"Creating cluster: {_testClusterIdentifier}");
- var createdCluster = await _redshiftWrapper!.CreateClusterAsync(
- _testClusterIdentifier!,
- TestDatabaseName,
- TestUsername,
- TestPassword);
-
- Assert.IsNotNull(createdCluster);
- Assert.AreEqual(_testClusterIdentifier, createdCluster.ClusterIdentifier);
- Console.WriteLine("Cluster creation initiated successfully.");
-
- // Step 2: Wait for cluster to become available (this can take several minutes)
- Console.WriteLine("Waiting for cluster to become available... This may take 10-15 minutes.");
- await WaitForClusterAvailable(_testClusterIdentifier!, TimeSpan.FromMinutes(20));
-
- // Step 3: List databases
- Console.WriteLine("Listing databases...");
- var databases = await _redshiftWrapper.ListDatabasesAsync(_testClusterIdentifier!, TestUsername, TestDatabaseName);
- Assert.IsNotNull(databases);
- Assert.IsTrue(databases.Count > 0);
- Console.WriteLine($"Found {databases.Count} databases.");
-
- // Step 4: Create table
- Console.WriteLine("Creating Movies table...");
- var createTableStatementId = await _redshiftWrapper.CreateTableAsync(
- _testClusterIdentifier!,
- TestDatabaseName,
- TestUsername);
- Assert.IsNotNull(createTableStatementId);
- Console.WriteLine("Movies table created successfully.");
-
- // Step 5: Insert sample data
- Console.WriteLine("Inserting sample movie data...");
- var insertStatementId = await _redshiftWrapper.InsertMovieAsync(
- _testClusterIdentifier!,
- TestDatabaseName,
- TestUsername,
- 1,
- "Test Movie",
- 2023);
- Assert.IsNotNull(insertStatementId);
- Console.WriteLine("Sample data inserted successfully.");
-
- // Step 6: Query data
- Console.WriteLine("Querying movies by year...");
- var movies = await _redshiftWrapper.QueryMoviesByYearAsync(
- _testClusterIdentifier!,
- TestDatabaseName,
- TestUsername,
- 2023);
- Assert.IsNotNull(movies);
- Assert.IsTrue(movies.Count > 0);
- Assert.AreEqual("Test Movie", movies[0]);
- Console.WriteLine($"Query returned {movies.Count} movies.");
-
- // Step 7: Modify cluster
- Console.WriteLine("Modifying cluster maintenance window...");
- var modifiedCluster = await _redshiftWrapper.ModifyClusterAsync(
- _testClusterIdentifier!,
- "wed:07:30-wed:08:00");
- Assert.IsNotNull(modifiedCluster);
- Console.WriteLine("Cluster modified successfully.");
-
- Console.WriteLine("Full workflow integration test completed successfully!");
- }
- finally
- {
- // Step 8: Clean up - Delete cluster
- if (!string.IsNullOrEmpty(_testClusterIdentifier))
- {
- Console.WriteLine($"Deleting test cluster: {_testClusterIdentifier}");
- try
- {
- var deletedCluster = await _redshiftWrapper!.DeleteClusterAsync(_testClusterIdentifier);
- Assert.IsNotNull(deletedCluster);
- Console.WriteLine("Cluster deletion initiated successfully.");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Failed to delete cluster: {ex.Message}");
- throw;
- }
- }
- }
- }
-
- [TestMethod]
- [TestCategory("Integration")]
- public async Task DescribeStatement_Integration_ReturnsStatementDetails()
- {
- // This test requires an existing cluster - skip if none available
- var clusters = await _redshiftWrapper!.DescribeClustersAsync();
-
- if (clusters.Count == 0)
- {
- Assert.Inconclusive("No Redshift clusters available for integration testing.");
- return;
- }
-
- var testCluster = clusters[0];
- if (testCluster.ClusterStatus != "available")
- {
- Assert.Inconclusive($"Test cluster {testCluster.ClusterIdentifier} is not available (status: {testCluster.ClusterStatus}).");
- return;
- }
-
- try
- {
- // Execute a simple statement
- var statementId = await _redshiftWrapper.CreateTableAsync(
- testCluster.ClusterIdentifier,
- TestDatabaseName,
- TestUsername);
-
- // Describe the statement
- var statementDetails = await _redshiftWrapper.DescribeStatementAsync(statementId);
-
- Assert.IsNotNull(statementDetails);
- Assert.AreEqual(statementId, statementDetails.Id);
- Assert.IsNotNull(statementDetails.Status);
-
- Console.WriteLine($"Statement {statementId} has status: {statementDetails.Status}");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Integration test failed: {ex.Message}");
- Assert.Inconclusive($"Could not complete integration test: {ex.Message}");
- }
- }
-
///
- /// Wait for a cluster to become available with timeout.
+ /// Verifies the scenario with an integration test. No errors should be logged.
///
- /// The cluster identifier.
- /// Maximum time to wait.
- private async Task WaitForClusterAvailable(string clusterIdentifier, TimeSpan timeout)
+ /// Async task.
+ [Fact]
+ [Trait("Category", "Integration")]
+ public async Task TestScenarioIntegration()
{
- var startTime = DateTime.UtcNow;
- var endTime = startTime.Add(timeout);
-
- while (DateTime.UtcNow < endTime)
- {
- var clusters = await _redshiftWrapper!.DescribeClustersAsync(clusterIdentifier);
-
- if (clusters.Count > 0 && clusters[0].ClusterStatus == "available")
- {
- Console.WriteLine($"Cluster {clusterIdentifier} is now available!");
- return;
- }
-
- var elapsed = DateTime.UtcNow - startTime;
- Console.WriteLine($"Waiting for cluster... Elapsed time: {elapsed:mm\\:ss}");
-
- await Task.Delay(TimeSpan.FromSeconds(30)); // Wait 30 seconds between checks
- }
-
- throw new TimeoutException($"Cluster {clusterIdentifier} did not become available within {timeout.TotalMinutes} minutes.");
- }
-}
+ // Arrange
+ RedshiftBasics.RedshiftBasics.IsInteractive = false;
-///
-/// Integration tests specifically for data operations.
-/// These tests require an existing, available Redshift cluster.
-///
-[TestClass]
-public class RedshiftDataIntegrationTests
-{
- private static RedshiftWrapper? _redshiftWrapper;
- private const string TestDatabaseName = "dev";
- private const string TestUsername = "testuser";
-
- [ClassInitialize]
- public static void ClassInitialize(TestContext context)
- {
- var redshiftClient = new AmazonRedshiftClient();
- var redshiftDataClient = new AmazonRedshiftDataAPIServiceClient();
- _redshiftWrapper = new RedshiftWrapper(redshiftClient, redshiftDataClient);
- }
-
- [TestMethod]
- [TestCategory("Integration")]
- [TestCategory("DataOperations")]
- public async Task DataOperations_WithExistingCluster_WorksCorrectly()
- {
- // Find an available cluster for testing
- var clusters = await _redshiftWrapper!.DescribeClustersAsync();
- var availableCluster = clusters.Find(c => c.ClusterStatus == "available");
+ var loggerScenarioMock = new Mock>();
- if (availableCluster == null)
- {
- Assert.Inconclusive("No available Redshift clusters found for data operations testing.");
- return;
- }
+ loggerScenarioMock.Setup(logger => logger.Log(
+ It.Is(logLevel => logLevel == LogLevel.Error),
+ It.IsAny(),
+ It.Is((@object, @type) => true),
+ It.IsAny(),
+ It.IsAny>()
+ ));
- var clusterIdentifier = availableCluster.ClusterIdentifier;
- Console.WriteLine($"Using cluster: {clusterIdentifier}");
-
- try
- {
- // Test creating a unique table
- var tableName = $"test_table_{DateTime.Now:yyyyMMddHHmmss}";
- Console.WriteLine($"Creating table: {tableName}");
-
- // Note: This would require modifying the wrapper to accept custom table names
- // For now, we'll test with the standard Movies table
-
- var createResult = await _redshiftWrapper.CreateTableAsync(
- clusterIdentifier,
- TestDatabaseName,
- TestUsername);
-
- Assert.IsNotNull(createResult);
- Console.WriteLine("Table creation test completed.");
-
- // Test data insertion
- var insertResult = await _redshiftWrapper.InsertMovieAsync(
- clusterIdentifier,
- TestDatabaseName,
- TestUsername,
- 999,
- $"Integration Test Movie {DateTime.Now:HHmmss}",
- 2023);
-
- Assert.IsNotNull(insertResult);
- Console.WriteLine("Data insertion test completed.");
-
- // Test data querying
- var queryResults = await _redshiftWrapper.QueryMoviesByYearAsync(
- clusterIdentifier,
- TestDatabaseName,
- TestUsername,
- 2023);
-
- Assert.IsNotNull(queryResults);
- Console.WriteLine($"Query returned {queryResults.Count} results.");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Data operations test failed: {ex.Message}");
- // Don't fail the test for expected database-related issues
- Assert.Inconclusive($"Data operations test could not complete: {ex.Message}");
- }
- }
-
- [TestMethod]
- [TestCategory("Integration")]
- public async Task ListDatabases_WithExistingCluster_ReturnsResults()
- {
- var clusters = await _redshiftWrapper!.DescribeClustersAsync();
- var availableCluster = clusters.Find(c => c.ClusterStatus == "available");
-
- if (availableCluster == null)
- {
- Assert.Inconclusive("No available Redshift clusters found for database listing test.");
- return;
- }
-
- try
- {
- var databases = await _redshiftWrapper.ListDatabasesAsync(
- availableCluster.ClusterIdentifier,
- TestUsername,
- TestDatabaseName);
-
- Assert.IsNotNull(databases);
- Console.WriteLine($"Found {databases.Count} databases in cluster {availableCluster.ClusterIdentifier}");
+ // Act
+ RedshiftBasics.RedshiftBasics.logger = loggerScenarioMock.Object;
- foreach (var db in databases)
- {
- Console.WriteLine($" Database: {db}");
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"List databases test failed: {ex.Message}");
- Assert.Inconclusive($"Could not list databases: {ex.Message}");
- }
+ // Act
+ RedshiftBasics.RedshiftBasics.Wrapper = new RedshiftWrapper(
+ new AmazonRedshiftClient(),
+ new AmazonRedshiftDataAPIServiceClient());
+
+ await RedshiftBasics.RedshiftBasics.RunScenarioAsync();
+
+ // Assert no errors logged
+ loggerScenarioMock.Verify(
+ logger => logger.Log(
+ It.Is(logLevel => logLevel == LogLevel.Error),
+ It.IsAny(),
+ It.Is((@object, @type) => true),
+ It.IsAny(),
+ It.IsAny>()),
+ Times.Never);
}
}
diff --git a/dotnetv4/Redshift/Tests/RedshiftTests.csproj b/dotnetv4/Redshift/Tests/RedshiftTests.csproj
index 9e1b2b84028..edcc870eb3b 100644
--- a/dotnetv4/Redshift/Tests/RedshiftTests.csproj
+++ b/dotnetv4/Redshift/Tests/RedshiftTests.csproj
@@ -9,19 +9,24 @@
-
-
-
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
From 036bf695a2832e53c31a6217305877425aafe84d Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Mon, 24 Nov 2025 15:46:40 -0600
Subject: [PATCH 06/16] Update format fixes and metadata.
---
.../.doc_gen/metadata/redshift_metadata.yaml | 26 +++++++++----------
dotnetv4/Redshift/Actions/HelloRedshift.cs | 2 +-
dotnetv4/Redshift/Actions/RedshiftWrapper.cs | 3 +--
dotnetv4/Redshift/Scenarios/RedshiftBasics.cs | 10 +++----
.../Tests/RedshiftIntegrationTests.cs | 2 +-
steering_docs/dotnet-tech/basics.md | 1 +
steering_docs/dotnet-tech/hello.md | 1 +
steering_docs/dotnet-tech/wrapper.md | 9 ++++---
8 files changed, 26 insertions(+), 28 deletions(-)
diff --git a/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml b/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
index 10abfd4d7fc..12d842387e9 100644
--- a/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
+++ b/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
@@ -13,7 +13,7 @@ redshift_CreateCluster:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.CreateCluster
+ - Redshift.dotnetv4.CreateCluster
services:
redshift: {CreateCluster}
@@ -30,7 +30,7 @@ redshift_DeleteCluster:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.DeleteCluster
+ - Redshift.dotnetv4.DeleteCluster
services:
redshift: {DeleteCluster}
@@ -47,7 +47,7 @@ redshift_DescribeClusters:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.DescribeClusters
+ - Redshift.dotnetv4.DescribeClusters
services:
redshift: {DescribeClusters}
@@ -64,7 +64,7 @@ redshift_ModifyCluster:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.ModifyCluster
+ - Redshift.dotnetv4.ModifyCluster
services:
redshift: {ModifyCluster}
@@ -81,7 +81,7 @@ redshift_CreateTable:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.CreateTable
+ - Redshift.dotnetv4.CreateTable
services:
redshift-data: {ExecuteStatement}
@@ -98,7 +98,7 @@ redshift_Insert:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.Insert
+ - Redshift.dotnetv4.Insert
services:
redshift-data: {ExecuteStatement}
@@ -115,7 +115,7 @@ redshift_Query:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.Query
+ - Redshift.dotnetv4.Query
services:
redshift-data: {ExecuteStatement, GetStatementResult}
@@ -132,7 +132,7 @@ redshift_DescribeStatement:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.DescribeStatement
+ - Redshift.dotnetv4.DescribeStatement
services:
redshift-data: {DescribeStatement}
@@ -149,7 +149,7 @@ redshift_GetStatementResult:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.GetStatementResult
+ - Redshift.dotnetv4.GetStatementResult
services:
redshift-data: {GetStatementResult}
@@ -166,7 +166,7 @@ redshift_ListDatabases:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.ListDatabases
+ - Redshift.dotnetv4.ListDatabases
services:
redshift-data: {ListDatabases}
@@ -183,7 +183,7 @@ redshift_Hello:
excerpts:
- description:
snippet_tags:
- - dotnetv4.example_code.redshift.Hello
+ - Redshift.dotnetv4.Hello
services:
redshift: {DescribeClusters}
@@ -200,10 +200,10 @@ redshift_Scenario:
excerpts:
- description: Create a Redshift wrapper class to manage operations.
snippet_tags:
- - dotnetv4.example_code.redshift.RedshiftWrapper
+ - Redshift.dotnetv4.RedshiftWrapper
- description: Run an interactive scenario demonstrating Redshift basics.
snippet_tags:
- - dotnetv4.example_code.redshift.RedshiftScenario
+ - Redshift.dotnetv4.RedshiftScenario
services:
redshift: {CreateCluster, DeleteCluster, DescribeClusters, ModifyCluster}
redshift-data: {ExecuteStatement, DescribeStatement, GetStatementResult, ListDatabases}
diff --git a/dotnetv4/Redshift/Actions/HelloRedshift.cs b/dotnetv4/Redshift/Actions/HelloRedshift.cs
index 56f2ef01d16..8fda12ecde1 100644
--- a/dotnetv4/Redshift/Actions/HelloRedshift.cs
+++ b/dotnetv4/Redshift/Actions/HelloRedshift.cs
@@ -65,4 +65,4 @@ public static async Task Main(string[] args)
}
}
// snippet-end:[Redshift.dotnetv4.Hello]
-}
+}
\ No newline at end of file
diff --git a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
index 67d7d436f97..2bbd9bdec5f 100644
--- a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
+++ b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
@@ -497,5 +497,4 @@ public async Task WaitForClusterAvailableAsync(string clusterIdentifier, bool is
Console.WriteLine($"Cluster is available! Total Elapsed Time: {totalElapsed:mm\\:ss}");
}
}
-// snippet-end:[Redshift.dotnetv4.RedshiftWrapper]
-
+// snippet-end:[Redshift.dotnetv4.RedshiftWrapper]
\ No newline at end of file
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
index 332fc5ac883..b4c2fe4df03 100644
--- a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
@@ -60,8 +60,6 @@ public static async Task RunScenarioAsync()
Console.WriteLine("Welcome to the Amazon Redshift SDK Getting Started scenario.");
Console.WriteLine(
"This .NET program demonstrates how to interact with Amazon Redshift by using the AWS SDK for .NET.");
- Console.WriteLine(
- "Upon completion of the program, all resources are cleaned up.");
Console.WriteLine("Let's get started...");
Console.WriteLine(
"================================================================================");
@@ -89,7 +87,6 @@ public static async Task RunScenarioAsync()
userPassword = passwordInput;
Console.WriteLine("================================================================================");
- Console.WriteLine("A Redshift cluster refers to the collection of computing resources and storage that work together to process and analyze large volumes of data.");
// Step 2: Get cluster identifier
Console.WriteLine("Enter a cluster id value (default is redshift-cluster-movies):");
@@ -147,7 +144,7 @@ public static async Task RunScenarioAsync()
// Step 7: Populate the Movies table
Console.WriteLine("================================================================================");
Console.WriteLine("Populate the Movies table using the Movies.json file.");
-
+
if (IsInteractive)
{
Console.WriteLine("Specify the number of records you would like to add to the Movies Table.");
@@ -176,7 +173,7 @@ public static async Task RunScenarioAsync()
// Step 8 & 9: Query movies by year
Console.WriteLine("================================================================================");
Console.WriteLine("Query the Movies table by year. Enter a value between 2012-2014.");
-
+
if (IsInteractive)
{
Console.Write("Enter a year: ");
@@ -282,5 +279,4 @@ private class Movie
public int Year { get; set; }
}
}
-// snippet-end:[Redshift.dotnetv4.RedshiftScenario]
-
+// snippet-end:[Redshift.dotnetv4.RedshiftScenario]
\ No newline at end of file
diff --git a/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
index 3d8da0e6216..89eb84f403c 100644
--- a/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
+++ b/dotnetv4/Redshift/Tests/RedshiftIntegrationTests.cs
@@ -58,4 +58,4 @@ public async Task TestScenarioIntegration()
It.IsAny>()),
Times.Never);
}
-}
+}
\ No newline at end of file
diff --git a/steering_docs/dotnet-tech/basics.md b/steering_docs/dotnet-tech/basics.md
index 0e2a71b1753..3cb3e80b3c1 100644
--- a/steering_docs/dotnet-tech/basics.md
+++ b/steering_docs/dotnet-tech/basics.md
@@ -667,6 +667,7 @@ public async Task CreateClusterAsync(...)
// ❌ WRONG - Old format
// snippet-start:[dotnetv4.example_code.redshift.CreateCluster]
+// snippet-end:[dotnetv4.example_code.redshift.CreateCluster]
```
**Format**: `[{Service}.dotnetv4.{ActionName}]`
diff --git a/steering_docs/dotnet-tech/hello.md b/steering_docs/dotnet-tech/hello.md
index 0b29ba1d278..2c9a0c61a32 100644
--- a/steering_docs/dotnet-tech/hello.md
+++ b/steering_docs/dotnet-tech/hello.md
@@ -187,6 +187,7 @@ public static async Task Main(string[] args)
// ❌ WRONG - Old format
// snippet-start:[dotnetv4.example_code.redshift.Hello]
+// snippet-end:[dotnetv4.example_code.redshift.Hello]
```
**Format**: `[{Service}.dotnetv4.Hello]`
diff --git a/steering_docs/dotnet-tech/wrapper.md b/steering_docs/dotnet-tech/wrapper.md
index b56d599e2e3..be54641f958 100644
--- a/steering_docs/dotnet-tech/wrapper.md
+++ b/steering_docs/dotnet-tech/wrapper.md
@@ -192,7 +192,7 @@ public class {Service}Wrapper
throw;
}
}
- // snippet-end:[{Service}.dotnetv4.List{Resources}WithFilter]
+ // snippet-end:[{Service}.dotnetv4.List{Resources}WithFilterSteering]
```
## Error Handling Requirements
@@ -289,15 +289,16 @@ await foreach (var response in itemsPaginator.Responses)
```csharp
// ✅ CORRECT - Service name first, then dotnetv4
-// snippet-start:[Redshift.dotnetv4.CreateCluster]
+// snippet-start:[Redshift.dotnetv4.CreateClusterSteering]
public async Task CreateClusterAsync(...)
{
// Implementation
}
-// snippet-end:[Redshift.dotnetv4.CreateCluster]
+// snippet-end:[Redshift.dotnetv4.CreateClusterSteering]
// ❌ WRONG - Old format
-// snippet-start:[dotnetv4.example_code.redshift.CreateCluster]
+// snippet-start:[dotnetv4.example_code.redshift.CreateClusterSteering]
+// snippet-end:[dotnetv4.example_code.redshift.CreateClusterSteering]
```
**Format**: `[{Service}.dotnetv4.{ActionName}]`
From 7cc2f059e7cd2b3637b637da64a3e7f513900f0d Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 08:45:10 -0600
Subject: [PATCH 07/16] Tag fixes in steering docs.
---
steering_docs/dotnet-tech/basics.md | 8 ++++----
steering_docs/dotnet-tech/wrapper.md | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/steering_docs/dotnet-tech/basics.md b/steering_docs/dotnet-tech/basics.md
index 3cb3e80b3c1..6b32383313a 100644
--- a/steering_docs/dotnet-tech/basics.md
+++ b/steering_docs/dotnet-tech/basics.md
@@ -658,16 +658,16 @@ namespace RedshiftActions
```csharp
// ✅ CORRECT - Service name first, then dotnetv4
-// snippet-start:[Redshift.dotnetv4.CreateCluster]
+// snippet-start:[Redshift.dotnetv4.CreateClusterSteering]
public async Task CreateClusterAsync(...)
{
// Implementation
}
-// snippet-end:[Redshift.dotnetv4.CreateCluster]
+// snippet-end:[Redshift.dotnetv4.CreateClusterSteering]
// ❌ WRONG - Old format
-// snippet-start:[dotnetv4.example_code.redshift.CreateCluster]
-// snippet-end:[dotnetv4.example_code.redshift.CreateCluster]
+// snippet-start:[dotnetv4.example_code.redshift.CreateClusterSteering]
+// snippet-end:[dotnetv4.example_code.redshift.CreateClusterSteering]
```
**Format**: `[{Service}.dotnetv4.{ActionName}]`
diff --git a/steering_docs/dotnet-tech/wrapper.md b/steering_docs/dotnet-tech/wrapper.md
index be54641f958..55c8b9e20e2 100644
--- a/steering_docs/dotnet-tech/wrapper.md
+++ b/steering_docs/dotnet-tech/wrapper.md
@@ -159,7 +159,7 @@ public class {Service}Wrapper
## Paginator with Parameters Pattern
```csharp
- // snippet-start:[{Service}.dotnetv4.List{Resources}WithFilter]
+ // snippet-start:[{Service}.dotnetv4.List{Resources}WithFilterSteering]
///
/// Lists {resources} with optional filtering, using pagination.
///
From a7f6c248875bbed3a70b3bcfdf683dee72f4f357 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 09:57:47 -0600
Subject: [PATCH 08/16] Fix for metadata location.
---
.doc_gen/metadata/redshift_metadata.yaml | 111 ++++++++++
.../.doc_gen/metadata/redshift_metadata.yaml | 209 ------------------
2 files changed, 111 insertions(+), 209 deletions(-)
delete mode 100644 dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
diff --git a/.doc_gen/metadata/redshift_metadata.yaml b/.doc_gen/metadata/redshift_metadata.yaml
index d9627603d72..d8b2e4384bf 100644
--- a/.doc_gen/metadata/redshift_metadata.yaml
+++ b/.doc_gen/metadata/redshift_metadata.yaml
@@ -5,6 +5,14 @@ redshift_Hello:
synopsis: get started using &RS;.
category: Hello
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.Hello
Go:
versions:
- sdk_version: 2
@@ -35,6 +43,14 @@ redshift_Hello:
redshift: {DescribeClusters}
redshift_ListDatabases:
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.ListDatabases
Java:
versions:
- sdk_version: 2
@@ -48,6 +64,14 @@ redshift_ListDatabases:
redshift: {ListDatabases}
redshift_CreateCluster:
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.CreateCluster
Go:
versions:
- sdk_version: 2
@@ -105,6 +129,14 @@ redshift_CreateCluster:
redshift: {CreateCluster}
redshift_DeleteCluster:
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.DeleteCluster
Go:
versions:
- sdk_version: 2
@@ -162,6 +194,14 @@ redshift_DeleteCluster:
redshift: {DeleteCluster}
redshift_DescribeClusters:
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.DescribeClusters
Go:
versions:
- sdk_version: 2
@@ -219,6 +259,14 @@ redshift_DescribeClusters:
redshift: {DescribeClusters}
redshift_ModifyCluster:
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.ModifyCluster
Go:
versions:
- sdk_version: 2
@@ -276,6 +324,14 @@ redshift_ModifyCluster:
redshift: {ModifyCluster}
redshift_DescribeStatement:
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.DescribeStatement
Java:
versions:
- sdk_version: 2
@@ -302,6 +358,14 @@ redshift_DescribeStatement:
redshift: {DescribeStatement}
redshift_GetStatementResult:
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.GetStatementResult
Java:
versions:
- sdk_version: 2
@@ -326,6 +390,42 @@ redshift_GetStatementResult:
- python.example_code.redshift_data.RedshiftDataWrapper.instantiation
services:
redshift: {GetStatementResult}
+redshift_CreateTable:
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.CreateTable
+ services:
+ redshift-data: {ExecuteStatement}
+redshift_Insert:
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.Insert
+ services:
+ redshift-data: {ExecuteStatement}
+redshift_Query:
+ languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description:
+ snippet_tags:
+ - Redshift.dotnetv4.Query
+ services:
+ redshift-data: {ExecuteStatement, GetStatementResult}
redshift_ExecuteStatement:
languages:
Java:
@@ -356,6 +456,17 @@ redshift_Scenario:
- Delete the Amazon Redshift cluster.
category: Basics
languages:
+ .NET:
+ versions:
+ - sdk_version: 4
+ github: dotnetv4/Redshift
+ excerpts:
+ - description: Create a Redshift wrapper class to manage operations.
+ snippet_tags:
+ - Redshift.dotnetv4.RedshiftWrapper
+ - description: Run an interactive scenario demonstrating Redshift basics.
+ snippet_tags:
+ - Redshift.dotnetv4.RedshiftScenario
Go:
versions:
- sdk_version: 2
diff --git a/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml b/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
deleted file mode 100644
index 12d842387e9..00000000000
--- a/dotnetv4/Redshift/.doc_gen/metadata/redshift_metadata.yaml
+++ /dev/null
@@ -1,209 +0,0 @@
-# Amazon Redshift code examples for the SDK for .NET Framework 4.x
-
-redshift_CreateCluster:
- title: Create an &RS; cluster
- title_abbrev: Create a cluster
- synopsis: create an &RS; cluster.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.CreateCluster
- services:
- redshift: {CreateCluster}
-
-redshift_DeleteCluster:
- title: Delete an &RS; cluster
- title_abbrev: Delete a cluster
- synopsis: delete an &RS; cluster.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.DeleteCluster
- services:
- redshift: {DeleteCluster}
-
-redshift_DescribeClusters:
- title: Describe &RS; clusters
- title_abbrev: Describe clusters
- synopsis: describe &RS; clusters.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.DescribeClusters
- services:
- redshift: {DescribeClusters}
-
-redshift_ModifyCluster:
- title: Modify an &RS; cluster
- title_abbrev: Modify a cluster
- synopsis: modify an &RS; cluster.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.ModifyCluster
- services:
- redshift: {ModifyCluster}
-
-redshift_CreateTable:
- title: Create a table in an &RS; database
- title_abbrev: Create a table
- synopsis: create a table in an &RS; database.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.CreateTable
- services:
- redshift-data: {ExecuteStatement}
-
-redshift_Insert:
- title: Insert data into an &RS; table
- title_abbrev: Insert data into a table
- synopsis: insert data into an &RS; table.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.Insert
- services:
- redshift-data: {ExecuteStatement}
-
-redshift_Query:
- title: Query data from an &RS; table
- title_abbrev: Query data from a table
- synopsis: query data from an &RS; table.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.Query
- services:
- redshift-data: {ExecuteStatement, GetStatementResult}
-
-redshift_DescribeStatement:
- title: Describe an &RS; statement execution
- title_abbrev: Describe a statement
- synopsis: describe an &RS; statement execution.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.DescribeStatement
- services:
- redshift-data: {DescribeStatement}
-
-redshift_GetStatementResult:
- title: Get results from an &RS; statement
- title_abbrev: Get statement results
- synopsis: get results from an &RS; statement.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.GetStatementResult
- services:
- redshift-data: {GetStatementResult}
-
-redshift_ListDatabases:
- title: List databases in an &RS; cluster
- title_abbrev: List databases
- synopsis: list databases in an &RS; cluster.
- category: Actions
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.ListDatabases
- services:
- redshift-data: {ListDatabases}
-
-redshift_Hello:
- title: Hello &RS;
- title_abbrev: Hello &RS;
- synopsis: get started using &RS;.
- category: Hello
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.Hello
- services:
- redshift: {DescribeClusters}
-
-redshift_Scenario:
- title: Get started with &RS; resources
- title_abbrev: Get started with resources
- synopsis: learn the basics of &RS; by creating clusters, databases, tables, and querying data.
- category: Scenarios
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description: Create a Redshift wrapper class to manage operations.
- snippet_tags:
- - Redshift.dotnetv4.RedshiftWrapper
- - description: Run an interactive scenario demonstrating Redshift basics.
- snippet_tags:
- - Redshift.dotnetv4.RedshiftScenario
- services:
- redshift: {CreateCluster, DeleteCluster, DescribeClusters, ModifyCluster}
- redshift-data: {ExecuteStatement, DescribeStatement, GetStatementResult, ListDatabases}
From 3492bbaa9f01e94926add64c9188db75b84883b7 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 10:53:55 -0600
Subject: [PATCH 09/16] Remove bad metadata.
---
.doc_gen/metadata/redshift_metadata.yaml | 36 ------------------------
1 file changed, 36 deletions(-)
diff --git a/.doc_gen/metadata/redshift_metadata.yaml b/.doc_gen/metadata/redshift_metadata.yaml
index d8b2e4384bf..fad23ba02fa 100644
--- a/.doc_gen/metadata/redshift_metadata.yaml
+++ b/.doc_gen/metadata/redshift_metadata.yaml
@@ -390,42 +390,6 @@ redshift_GetStatementResult:
- python.example_code.redshift_data.RedshiftDataWrapper.instantiation
services:
redshift: {GetStatementResult}
-redshift_CreateTable:
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.CreateTable
- services:
- redshift-data: {ExecuteStatement}
-redshift_Insert:
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.Insert
- services:
- redshift-data: {ExecuteStatement}
-redshift_Query:
- languages:
- .NET:
- versions:
- - sdk_version: 4
- github: dotnetv4/Redshift
- excerpts:
- - description:
- snippet_tags:
- - Redshift.dotnetv4.Query
- services:
- redshift-data: {ExecuteStatement, GetStatementResult}
redshift_ExecuteStatement:
languages:
Java:
From c669be018b11d8a2d757faab50dff9b536871b7a Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 12:16:59 -0600
Subject: [PATCH 10/16] Tag fixes.
---
steering_docs/dotnet-tech/basics.md | 4 ++--
steering_docs/dotnet-tech/hello.md | 8 ++++----
steering_docs/dotnet-tech/wrapper.md | 8 ++++----
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/steering_docs/dotnet-tech/basics.md b/steering_docs/dotnet-tech/basics.md
index 6b32383313a..b1d0ed452da 100644
--- a/steering_docs/dotnet-tech/basics.md
+++ b/steering_docs/dotnet-tech/basics.md
@@ -658,12 +658,12 @@ namespace RedshiftActions
```csharp
// ✅ CORRECT - Service name first, then dotnetv4
-// snippet-start:[Redshift.dotnetv4.CreateClusterSteering]
+// snippet-start:[{Service}.dotnetv4.CreateClusterSteering]
public async Task CreateClusterAsync(...)
{
// Implementation
}
-// snippet-end:[Redshift.dotnetv4.CreateClusterSteering]
+// snippet-end:[{Service}.dotnetv4.CreateClusterSteering]
// ❌ WRONG - Old format
// snippet-start:[dotnetv4.example_code.redshift.CreateClusterSteering]
diff --git a/steering_docs/dotnet-tech/hello.md b/steering_docs/dotnet-tech/hello.md
index 2c9a0c61a32..ef0ced5e6b4 100644
--- a/steering_docs/dotnet-tech/hello.md
+++ b/steering_docs/dotnet-tech/hello.md
@@ -178,16 +178,16 @@ public class Hello{Service}
```csharp
// ✅ CORRECT
-// snippet-start:[Redshift.dotnetv4.Hello]
+// snippet-start:[{Service}.dotnetv4.HelloSteering]
public static async Task Main(string[] args)
{
// Implementation
}
-// snippet-end:[Redshift.dotnetv4.Hello]
+// snippet-end:[{Service}.dotnetv4.Hello]
// ❌ WRONG - Old format
-// snippet-start:[dotnetv4.example_code.redshift.Hello]
-// snippet-end:[dotnetv4.example_code.redshift.Hello]
+// snippet-start:[dotnetv4.example_code.{Service}.HelloSteering]
+// snippet-end:[dotnetv4.example_code.{Service}.HelloSteering]
```
**Format**: `[{Service}.dotnetv4.Hello]`
diff --git a/steering_docs/dotnet-tech/wrapper.md b/steering_docs/dotnet-tech/wrapper.md
index 55c8b9e20e2..93e215453ad 100644
--- a/steering_docs/dotnet-tech/wrapper.md
+++ b/steering_docs/dotnet-tech/wrapper.md
@@ -289,16 +289,16 @@ await foreach (var response in itemsPaginator.Responses)
```csharp
// ✅ CORRECT - Service name first, then dotnetv4
-// snippet-start:[Redshift.dotnetv4.CreateClusterSteering]
+// snippet-start:[{Service}.dotnetv4.CreateClusterSteering]
public async Task CreateClusterAsync(...)
{
// Implementation
}
-// snippet-end:[Redshift.dotnetv4.CreateClusterSteering]
+// snippet-end:[{Service}.dotnetv4.CreateClusterSteering]
// ❌ WRONG - Old format
-// snippet-start:[dotnetv4.example_code.redshift.CreateClusterSteering]
-// snippet-end:[dotnetv4.example_code.redshift.CreateClusterSteering]
+// snippet-start:[dotnetv4.example_code.{Service}.CreateClusterSteering]
+// snippet-end:[dotnetv4.example_code.{Service}.CreateClusterSteering]
```
**Format**: `[{Service}.dotnetv4.{ActionName}]`
From 6755c50176b1edf63c473e340df22058bdfb16e6 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 12:24:29 -0600
Subject: [PATCH 11/16] Update hello.md
---
steering_docs/dotnet-tech/hello.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/steering_docs/dotnet-tech/hello.md b/steering_docs/dotnet-tech/hello.md
index ef0ced5e6b4..4e4b9278042 100644
--- a/steering_docs/dotnet-tech/hello.md
+++ b/steering_docs/dotnet-tech/hello.md
@@ -183,7 +183,7 @@ public static async Task Main(string[] args)
{
// Implementation
}
-// snippet-end:[{Service}.dotnetv4.Hello]
+// snippet-end:[{Service}.dotnetv4.HelloSteering]
// ❌ WRONG - Old format
// snippet-start:[dotnetv4.example_code.{Service}.HelloSteering]
From 605294336e81dbad534dd5d753781cab3a65a111 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 12:29:24 -0600
Subject: [PATCH 12/16] Update README.md
---
dotnetv4/Redshift/README.md | 132 +++++++++++++++++-------------------
1 file changed, 61 insertions(+), 71 deletions(-)
diff --git a/dotnetv4/Redshift/README.md b/dotnetv4/Redshift/README.md
index 4b994bccafc..6c0011b904d 100644
--- a/dotnetv4/Redshift/README.md
+++ b/dotnetv4/Redshift/README.md
@@ -1,129 +1,119 @@
-# Amazon Redshift code examples for the SDK for .NET Framework 4.x
+# Amazon Redshift code examples for the SDK for .NET (v4)
## Overview
-This folder contains code examples that demonstrate how to use the AWS SDK for .NET Framework 4.x to interact with Amazon Redshift.
+Shows how to use the AWS SDK for .NET (v4) to work with Amazon Redshift.
-Amazon Redshift is a fast, fully managed, petabyte-scale data warehouse service that makes it simple and cost-effective to efficiently analyze all your data using your existing business intelligence tools.
+
+
+
+_Amazon Redshift is a fast, fully managed, petabyte-scale data warehouse service that makes it simple and cost-effective to efficiently analyze all your data using your existing business intelligence tools._
## ⚠ Important
-* Running this code might result in charges to your AWS account.
+* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/).
* Running the tests might result in charges to your AWS account.
* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).
* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services).
+
+
+
## Code examples
-### Actions
+### Prerequisites
-Code excerpts that show you how to call individual service functions:
+For prerequisites, see the [README](../README.md#Prerequisites) in the `dotnetv4` folder.
-- [CreateCluster](Actions/RedshiftWrapper.cs#L28) (`CreateCluster`)
-- [DeleteCluster](Actions/RedshiftWrapper.cs#L118) (`DeleteCluster`)
-- [DescribeClusters](Actions/RedshiftWrapper.cs#L69) (`DescribeClusters`)
-- [ModifyCluster](Actions/RedshiftWrapper.cs#L96) (`ModifyCluster`)
-- [CreateTable](Actions/RedshiftWrapper.cs#L157) (`ExecuteStatement`)
-- [InsertMovie](Actions/RedshiftWrapper.cs#L193) (`ExecuteStatement`)
-- [QueryMoviesByYear](Actions/RedshiftWrapper.cs#L227) (`ExecuteStatement`, `GetStatementResult`)
-- [DescribeStatement](Actions/RedshiftWrapper.cs#L269) (`DescribeStatement`)
-- [GetStatementResult](Actions/RedshiftWrapper.cs#L289) (`GetStatementResult`)
-- [ListDatabases](Actions/RedshiftWrapper.cs#L130) (`ListDatabases`)
-### Scenarios
+
+
-Code examples that show you how to accomplish a specific task by calling multiple functions within the same service:
+### Get started
-- [Get started with Redshift clusters](Scenarios/RedshiftBasics.cs) - Learn the basics of Amazon Redshift by creating a cluster, adding a table, inserting data, and querying the table.
+- [Hello Amazon Redshift](Actions/HelloRedshift.cs#L20) (`DescribeClusters`)
-### Hello
-- [Hello Amazon Redshift](Hello/HelloRedshift.cs) - A simple example that shows how to get started with Amazon Redshift by listing existing clusters.
+### Basics
-## Run the examples
+Code examples that show you how to perform the essential operations within a service.
-### Prerequisites
+- [Learn the basics](Actions/RedshiftWrapper.cs)
-For general prerequisites, see the [README](../../README.md) in the `dotnetv4` folder.
-After the example compiles, you can run it from the command line. To do so, navigate to the folder that contains the .csproj file and run the following command:
+### Single actions
-```
-dotnet run
-```
+Code excerpts that show you how to call individual service functions.
-Alternatively, you can run the example from within your IDE.
+- [CreateCluster](Actions/RedshiftWrapper.cs#L34)
+- [DeleteCluster](Actions/RedshiftWrapper.cs#L143)
+- [DescribeClusters](Actions/RedshiftWrapper.cs#L77)
+- [DescribeStatement](Actions/RedshiftWrapper.cs#L375)
+- [GetStatementResult](Actions/RedshiftWrapper.cs#L406)
+- [ListDatabases](Actions/RedshiftWrapper.cs#L176)
+- [ModifyCluster](Actions/RedshiftWrapper.cs#L109)
-### Hello Amazon Redshift
-This example shows you how to get started using Amazon Redshift.
+
+
-#### Purpose
+## Run the examples
-Shows how to use the AWS SDK for .NET to get started using Amazon Redshift. Lists existing Redshift clusters in your account.
+### Instructions
-### Redshift Basics Scenario
-This scenario demonstrates how to interact with Amazon Redshift using the AWS SDK for .NET. It demonstrates various tasks such as creating a Redshift cluster, verifying its readiness, creating a table, populating the table with data, executing SQL queries, and finally cleaning up resources.
+
+
-#### Purpose
+#### Hello Amazon Redshift
-Demonstrates how to:
+This example shows you how to get started using Amazon Redshift.
-1. Create an Amazon Redshift cluster.
-2. Wait for the cluster to become available.
-3. List databases in the cluster.
-4. Create a "Movies" table.
-5. Populate the "Movies" table using sample data.
-6. Query the "Movies" table by year.
-7. Modify the Redshift cluster.
-8. Delete the Amazon Redshift cluster.
-#### Usage
+#### Learn the basics
-1. Clone the repository or download the source code files.
-2. Open the code in your preferred .NET IDE.
-3. Update the following variables in the `RunScenarioAsync()` method if needed:
- - `userName`: The username for the Redshift cluster.
- - `userPassword`: The password for the Redshift cluster.
- - `databaseName`: The name of the database to use ("dev").
-4. Run the `RedshiftBasics` class.
+This example shows you how to do the following:
-The program will guide you through the scenario, prompting you to enter a cluster ID and the number of records to add to the "Movies" table. The program will also display the progress and results of the various operations.
+- Create a Redshift cluster.
+- List databases in the cluster.
+- Create a table named Movies.
+- Populate the Movies table.
+- Query the Movies table by year.
+- Modify the Redshift cluster.
+- Delete the Amazon Redshift cluster.
-## Tests
+
+
-### Unit tests
-Unit tests in this solution use MSTest. The tests use Moq to mock AWS service client dependencies.
+
+
-Run unit tests with this command:
-```
-dotnet test Tests/RedshiftTests.csproj
-```
+### Tests
-### Integration tests
+⚠ Running tests might result in charges to your AWS account.
-⚠️ Running the integration tests might result in charges to your AWS account.
-The integration tests in this solution require access to AWS services and will create and delete AWS resources. Make sure you have valid AWS credentials configured before running these tests.
+To find instructions for running these tests, see the [README](../README.md#Tests)
+in the `dotnetv4` folder.
-Run integration tests with this command:
-```
-dotnet test IntegrationTests/RedshiftIntegrationTests.csproj
-```
-Note: The full workflow integration test can take 10-15 minutes to complete due to cluster creation time.
+
+
## Additional resources
- [Amazon Redshift Management Guide](https://docs.aws.amazon.com/redshift/latest/mgmt/welcome.html)
- [Amazon Redshift API Reference](https://docs.aws.amazon.com/redshift/latest/APIReference/Welcome.html)
-- [SDK for .NET Amazon Redshift reference](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/items/Redshift/NRedshift.html)
+- [SDK for .NET (v4) Amazon Redshift reference](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/Redshift/NRedshift.html)
+
+
+
---
-Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
SPDX-License-Identifier: Apache-2.0
From ed4cc8a80afba5b01f0b3cf46a0bdf7e969775e9 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 16:28:44 -0600
Subject: [PATCH 13/16] Cleanups.
---
dotnetv4/Redshift/Actions/HelloRedshift.cs | 9 +--
dotnetv4/Redshift/Actions/RedshiftWrapper.cs | 57 ++++++++++---------
dotnetv4/Redshift/Scenarios/RedshiftBasics.cs | 35 ++++++------
3 files changed, 50 insertions(+), 51 deletions(-)
diff --git a/dotnetv4/Redshift/Actions/HelloRedshift.cs b/dotnetv4/Redshift/Actions/HelloRedshift.cs
index 8fda12ecde1..2527f82d68b 100644
--- a/dotnetv4/Redshift/Actions/HelloRedshift.cs
+++ b/dotnetv4/Redshift/Actions/HelloRedshift.cs
@@ -26,12 +26,7 @@ public static async Task Main(string[] args)
{
var redshiftClient = new AmazonRedshiftClient();
- logger = LoggerFactory.Create(builder => { builder.AddConsole(); })
- .CreateLogger();
-
- Console.Clear();
Console.WriteLine("Hello, Amazon Redshift! Let's list available clusters:");
- Console.WriteLine();
var clusters = new List();
@@ -55,12 +50,10 @@ public static async Task Main(string[] args)
}
catch (AmazonRedshiftException ex)
{
- logger.LogError("Error listing clusters: {Message}", ex.Message);
- Console.WriteLine($"Couldn't list clusters. Error: {ex.Message}");
+ Console.WriteLine($"Couldn't list clusters. Here's why: {ex.Message}");
}
catch (Exception ex)
{
- logger.LogError("An error occurred: {Message}", ex.Message);
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
diff --git a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
index 2bbd9bdec5f..acab6f29170 100644
--- a/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
+++ b/dotnetv4/Redshift/Actions/RedshiftWrapper.cs
@@ -84,18 +84,31 @@ public async Task> DescribeClustersAsync(string? clusterIdentifier
{
try
{
+ var clusters = new List();
var request = new DescribeClustersRequest();
if (!string.IsNullOrEmpty(clusterIdentifier))
{
request.ClusterIdentifier = clusterIdentifier;
}
- var response = await _redshiftClient.DescribeClustersAsync(request);
- return response.Clusters;
+ var clustersPaginator = _redshiftClient.Paginators.DescribeClusters(request);
+ await foreach (var response in clustersPaginator.Responses)
+ {
+ if (response.Clusters != null)
+ clusters.AddRange(response.Clusters);
+ }
+
+ Console.WriteLine($"{clusters.Count} cluster(s) retrieved.");
+ foreach (var cluster in clusters)
+ {
+ Console.WriteLine($"\t{cluster.ClusterIdentifier} (Status: {cluster.ClusterStatus})");
+ }
+
+ return clusters;
}
catch (ClusterNotFoundException ex)
{
- Console.WriteLine($"Cluster not found: {ex.Message}");
+ Console.WriteLine($"Cluster {clusterIdentifier} not found: {ex.Message}");
throw;
}
catch (Exception ex)
@@ -112,8 +125,8 @@ public async Task> DescribeClustersAsync(string? clusterIdentifier
///
/// The identifier for the cluster.
/// The preferred maintenance window.
- /// The modified cluster.
- public async Task ModifyClusterAsync(string clusterIdentifier, string preferredMaintenanceWindow)
+ /// True if successful.
+ public async Task ModifyClusterAsync(string clusterIdentifier, string preferredMaintenanceWindow)
{
try
{
@@ -124,29 +137,29 @@ public async Task ModifyClusterAsync(string clusterIdentifier, string p
};
var response = await _redshiftClient.ModifyClusterAsync(request);
- Console.WriteLine($"The modified cluster was successfully modified and has {preferredMaintenanceWindow} as the maintenance window");
- return response.Cluster;
+ Console.WriteLine($"The modified cluster was successfully modified and has {response.Cluster.PreferredMaintenanceWindow} as the maintenance window");
+ return true;
}
catch (ClusterNotFoundException ex)
{
- Console.WriteLine($"Cluster not found: {ex.Message}");
- throw;
+ Console.WriteLine($"Cluster {clusterIdentifier} not found: {ex.Message}");
+ return false;
}
catch (Exception ex)
{
Console.WriteLine($"Couldn't modify cluster. Here's why: {ex.Message}");
- throw;
+ return false;
}
}
// snippet-end:[Redshift.dotnetv4.ModifyCluster]
// snippet-start:[Redshift.dotnetv4.DeleteCluster]
///
- /// Delete an Amazon Redshift cluster.
+ /// Delete an Amazon Redshift cluster without a final snapshot.
///
/// The identifier for the cluster.
- /// The deleted cluster.
- public async Task DeleteClusterAsync(string clusterIdentifier)
+ /// True if successful.
+ public async Task DeleteClusterWithoutSnapshotAsync(string clusterIdentifier)
{
try
{
@@ -158,17 +171,17 @@ public async Task DeleteClusterAsync(string clusterIdentifier)
var response = await _redshiftClient.DeleteClusterAsync(request);
Console.WriteLine($"The {clusterIdentifier} was deleted");
- return response.Cluster;
+ return true;
}
catch (ClusterNotFoundException ex)
{
Console.WriteLine($"Cluster not found: {ex.Message}");
- throw;
+ return false;
}
catch (Exception ex)
{
Console.WriteLine($"Couldn't delete cluster. Here's why: {ex.Message}");
- throw;
+ return false;
}
}
// snippet-end:[Redshift.dotnetv4.DeleteCluster]
@@ -468,18 +481,10 @@ private async Task WaitForStatementToCompleteAsync(string statementId)
/// Wait for a cluster to become available.
///
/// The cluster identifier.
- /// Whether to prompt for user input.
/// A task representing the asynchronous operation.
- public async Task WaitForClusterAvailableAsync(string clusterIdentifier, bool isInteractive = true)
+ public async Task WaitForClusterAvailableAsync(string clusterIdentifier)
{
- Console.WriteLine($"Wait until {clusterIdentifier} is available.");
- if (isInteractive)
- {
- Console.WriteLine("Press Enter to continue...");
- Console.ReadLine();
- }
-
- Console.WriteLine("Waiting for cluster to become available. This may take a few minutes.");
+ Console.WriteLine($"Wait until {clusterIdentifier} is available. This may take a few minutes.");
var startTime = DateTime.Now;
var clusters = await DescribeClustersAsync(clusterIdentifier);
diff --git a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
index b4c2fe4df03..6adab87011f 100644
--- a/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
+++ b/dotnetv4/Redshift/Scenarios/RedshiftBasics.cs
@@ -53,6 +53,13 @@ public static async Task Main(string[] args)
///
public static async Task RunScenarioAsync()
{
+ // Set all variables to default values
+ string userName = "awsuser";
+ string userPassword = "AwsUser1000";
+ string clusterIdentifier = "redshift-cluster-movies";
+ var databaseName = "dev";
+ int recordCount = 50;
+ int year = 2013;
try
{
Console.WriteLine(
@@ -64,24 +71,16 @@ public static async Task RunScenarioAsync()
Console.WriteLine(
"================================================================================");
- // Set all variables to default values
- string userName = "awsuser";
- string userPassword = "AwsUser1000";
- string clusterIdentifier = "redshift-cluster-movies";
- var databaseName = "dev";
- int recordCount = 50;
- int year = 2013;
-
// Step 1: Get user credentials (if interactive)
if (IsInteractive)
{
- Console.WriteLine("Please enter your user name (default is awsuser):");
+ Console.WriteLine("Please enter a user name for the cluster (default is awsuser):");
var userInput = Console.ReadLine();
if (!string.IsNullOrEmpty(userInput))
userName = userInput;
Console.WriteLine("================================================================================");
- Console.WriteLine("Please enter your user password (default is AwsUser1000):");
+ Console.WriteLine("Please enter a user password for the cluster (default is AwsUser1000):");
var passwordInput = Console.ReadLine();
if (!string.IsNullOrEmpty(passwordInput))
userPassword = passwordInput;
@@ -105,7 +104,7 @@ public static async Task RunScenarioAsync()
// Step 4: Wait for cluster to become available
Console.WriteLine("================================================================================");
- await Wrapper.WaitForClusterAvailableAsync(clusterIdentifier, IsInteractive);
+ await Wrapper.WaitForClusterAvailableAsync(clusterIdentifier);
Console.WriteLine("================================================================================");
// Step 5: List databases
@@ -152,7 +151,7 @@ public static async Task RunScenarioAsync()
Console.Write("Enter a value: ");
var recordCountInput = Console.ReadLine();
- if (int.TryParse(recordCountInput, out var inputCount) && inputCount >= 50 && inputCount <= 200)
+ if (int.TryParse(recordCountInput, out var inputCount) && inputCount is >= 50 and <= 200)
{
recordCount = inputCount;
}
@@ -178,7 +177,7 @@ public static async Task RunScenarioAsync()
{
Console.Write("Enter a year: ");
var yearInput = Console.ReadLine();
- if (int.TryParse(yearInput, out var inputYear) && inputYear >= 2012 && inputYear <= 2014)
+ if (int.TryParse(yearInput, out var inputYear) && inputYear is >= 2012 and <= 2014)
{
year = inputYear;
}
@@ -212,15 +211,15 @@ public static async Task RunScenarioAsync()
{
Console.WriteLine("Would you like to delete the Amazon Redshift cluster? (y/n)");
var deleteResponse = Console.ReadLine();
- if (deleteResponse?.ToLower() == "y" || deleteResponse?.ToLower() == "yes")
+ if (deleteResponse?.ToLower() == "y")
{
- await Wrapper.DeleteClusterAsync(clusterIdentifier);
+ await Wrapper.DeleteClusterWithoutSnapshotAsync(clusterIdentifier);
}
}
else
{
- Console.WriteLine("Deleting the Amazon Redshift cluster (non-interactive mode)...");
- await Wrapper.DeleteClusterAsync(clusterIdentifier);
+ Console.WriteLine("Deleting the Amazon Redshift cluster...");
+ await Wrapper.DeleteClusterWithoutSnapshotAsync(clusterIdentifier);
}
Console.WriteLine("================================================================================");
@@ -231,6 +230,8 @@ public static async Task RunScenarioAsync()
catch (Exception ex)
{
Console.WriteLine($"An error occurred during the scenario: {ex.Message}");
+ Console.WriteLine("Deleting the Amazon Redshift cluster...");
+ await Wrapper!.DeleteClusterWithoutSnapshotAsync(clusterIdentifier);
throw;
}
}
From 9b09bade9b51e40c512873de629d08a7dfa5d2c2 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 16:33:38 -0600
Subject: [PATCH 14/16] Update README.md
---
dotnetv4/Redshift/README.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/dotnetv4/Redshift/README.md b/dotnetv4/Redshift/README.md
index 6c0011b904d..6965d9bd97f 100644
--- a/dotnetv4/Redshift/README.md
+++ b/dotnetv4/Redshift/README.md
@@ -46,12 +46,12 @@ Code examples that show you how to perform the essential operations within a ser
Code excerpts that show you how to call individual service functions.
- [CreateCluster](Actions/RedshiftWrapper.cs#L34)
-- [DeleteCluster](Actions/RedshiftWrapper.cs#L143)
+- [DeleteCluster](Actions/RedshiftWrapper.cs#L156)
- [DescribeClusters](Actions/RedshiftWrapper.cs#L77)
-- [DescribeStatement](Actions/RedshiftWrapper.cs#L375)
-- [GetStatementResult](Actions/RedshiftWrapper.cs#L406)
-- [ListDatabases](Actions/RedshiftWrapper.cs#L176)
-- [ModifyCluster](Actions/RedshiftWrapper.cs#L109)
+- [DescribeStatement](Actions/RedshiftWrapper.cs#L388)
+- [GetStatementResult](Actions/RedshiftWrapper.cs#L419)
+- [ListDatabases](Actions/RedshiftWrapper.cs#L189)
+- [ModifyCluster](Actions/RedshiftWrapper.cs#L122)
From fb81ba45b14d40c8864cdd09e79660ed20cc9f41 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 16:48:42 -0600
Subject: [PATCH 15/16] Update DotNetV4Examples.sln
---
dotnetv4/DotNetV4Examples.sln | 441 +++++++++++++++++++++++++++++++++-
1 file changed, 435 insertions(+), 6 deletions(-)
diff --git a/dotnetv4/DotNetV4Examples.sln b/dotnetv4/DotNetV4Examples.sln
index e4e1cf6f809..820afe0cfbe 100644
--- a/dotnetv4/DotNetV4Examples.sln
+++ b/dotnetv4/DotNetV4Examples.sln
@@ -63,7 +63,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Command_R_InvokeModelWithRe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Command_R_InvokeModel", "Bedrock-runtime\Models\CohereCommand\Command_R_InvokeModel\Command_R_InvokeModel.csproj", "{6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}"
EndProject
-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AnthropicClaude", "AnthropicClaude", "{6FF2EDB6-D1B8-4EE0-B1F0-2BCE66972E39}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvokeModelWithResponseStream", "Bedrock-runtime\Models\AnthropicClaude\InvokeModelWithResponseStream\InvokeModelWithResponseStream.csproj", "{345DA0D1-C762-49EF-9953-6F4D57CB7FC7}"
@@ -76,7 +75,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Converse", "Bedrock-runtime
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AmazonTitanText", "AmazonTitanText", "{74979310-8A92-47DC-B5CA-EFA7970E1202}"
EndProject
-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BedrockRuntimeActions", "Bedrock-runtime\Actions\BedrockRuntimeActions.csproj", "{05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CloudFormation", "CloudFormation", "{5FBEAD92-9234-4824-9320-2052D236C9CD}"
@@ -147,206 +145,636 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "S3\Scenarios\S3_CreatePresignedPost\Basics.csproj", "{2B6F24A0-4569-E8A2-81B4-3925FA4F0320}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Redshift", "Redshift", "{BC1690DE-FD9E-72EA-CAED-A2B9A3D6B335}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedshiftActions", "Redshift\Actions\RedshiftActions.csproj", "{4E74F2DB-3BA5-4390-8FBF-C58F57601671}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedshiftBasics", "Redshift\Scenarios\RedshiftBasics.csproj", "{A30F8E57-AF18-40EC-B130-140585246CC7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedshiftTests", "Redshift\Tests\RedshiftTests.csproj", "{1DB37AC5-18FE-4535-816C-827B4D3DCB96}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{44801438-44F3-4B57-8A5E-3C788A9B2686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44801438-44F3-4B57-8A5E-3C788A9B2686}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Debug|x64.Build.0 = Debug|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Debug|x86.Build.0 = Debug|Any CPU
{44801438-44F3-4B57-8A5E-3C788A9B2686}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44801438-44F3-4B57-8A5E-3C788A9B2686}.Release|Any CPU.Build.0 = Release|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Release|x64.ActiveCfg = Release|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Release|x64.Build.0 = Release|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Release|x86.ActiveCfg = Release|Any CPU
+ {44801438-44F3-4B57-8A5E-3C788A9B2686}.Release|x86.Build.0 = Release|Any CPU
{67C93719-C71C-44C3-8677-BF9C7CD093B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67C93719-C71C-44C3-8677-BF9C7CD093B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Debug|x64.Build.0 = Debug|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Debug|x86.Build.0 = Debug|Any CPU
{67C93719-C71C-44C3-8677-BF9C7CD093B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67C93719-C71C-44C3-8677-BF9C7CD093B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Release|x64.ActiveCfg = Release|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Release|x64.Build.0 = Release|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Release|x86.ActiveCfg = Release|Any CPU
+ {67C93719-C71C-44C3-8677-BF9C7CD093B6}.Release|x86.Build.0 = Release|Any CPU
{33E362E5-40F3-4C90-A229-44EF3E2389EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33E362E5-40F3-4C90-A229-44EF3E2389EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Debug|x64.Build.0 = Debug|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Debug|x86.Build.0 = Debug|Any CPU
{33E362E5-40F3-4C90-A229-44EF3E2389EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33E362E5-40F3-4C90-A229-44EF3E2389EF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Release|x64.ActiveCfg = Release|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Release|x64.Build.0 = Release|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Release|x86.ActiveCfg = Release|Any CPU
+ {33E362E5-40F3-4C90-A229-44EF3E2389EF}.Release|x86.Build.0 = Release|Any CPU
{28C9796E-9E28-4715-BD44-82CCF4BF8797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28C9796E-9E28-4715-BD44-82CCF4BF8797}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Debug|x64.Build.0 = Debug|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Debug|x86.Build.0 = Debug|Any CPU
{28C9796E-9E28-4715-BD44-82CCF4BF8797}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28C9796E-9E28-4715-BD44-82CCF4BF8797}.Release|Any CPU.Build.0 = Release|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Release|x64.ActiveCfg = Release|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Release|x64.Build.0 = Release|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Release|x86.ActiveCfg = Release|Any CPU
+ {28C9796E-9E28-4715-BD44-82CCF4BF8797}.Release|x86.Build.0 = Release|Any CPU
{96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Debug|x64.Build.0 = Debug|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Debug|x86.Build.0 = Debug|Any CPU
{96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Release|x64.ActiveCfg = Release|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Release|x64.Build.0 = Release|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Release|x86.ActiveCfg = Release|Any CPU
+ {96B016E8-CDB3-490B-A1BB-6A9008E9E30B}.Release|x86.Build.0 = Release|Any CPU
{18B07F62-06CC-4562-BB86-2C072758B90F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18B07F62-06CC-4562-BB86-2C072758B90F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Debug|x64.Build.0 = Debug|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Debug|x86.Build.0 = Debug|Any CPU
{18B07F62-06CC-4562-BB86-2C072758B90F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18B07F62-06CC-4562-BB86-2C072758B90F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Release|x64.ActiveCfg = Release|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Release|x64.Build.0 = Release|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Release|x86.ActiveCfg = Release|Any CPU
+ {18B07F62-06CC-4562-BB86-2C072758B90F}.Release|x86.Build.0 = Release|Any CPU
{F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Debug|x64.Build.0 = Debug|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Debug|x86.Build.0 = Debug|Any CPU
{F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Release|x64.ActiveCfg = Release|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Release|x64.Build.0 = Release|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Release|x86.ActiveCfg = Release|Any CPU
+ {F98B4D67-5A92-4D66-9DAA-8334D65E23B1}.Release|x86.Build.0 = Release|Any CPU
{C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Debug|x64.Build.0 = Debug|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Debug|x86.Build.0 = Debug|Any CPU
{C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Release|x64.ActiveCfg = Release|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Release|x64.Build.0 = Release|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Release|x86.ActiveCfg = Release|Any CPU
+ {C1A6A3FD-5ADD-4489-92E3-D888F256B74A}.Release|x86.Build.0 = Release|Any CPU
{F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Debug|x64.Build.0 = Debug|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Debug|x86.Build.0 = Debug|Any CPU
{F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Release|x64.ActiveCfg = Release|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Release|x64.Build.0 = Release|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Release|x86.ActiveCfg = Release|Any CPU
+ {F8B5BC77-F8BF-45E8-8E12-7E197F925772}.Release|x86.Build.0 = Release|Any CPU
{37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Debug|x64.Build.0 = Debug|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Debug|x86.Build.0 = Debug|Any CPU
{37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Release|x64.ActiveCfg = Release|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Release|x64.Build.0 = Release|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Release|x86.ActiveCfg = Release|Any CPU
+ {37CACA7D-D3BE-42AF-A8C2-639E16C03BC4}.Release|x86.Build.0 = Release|Any CPU
{7B624438-4340-4333-B2F6-2ADA7A93006C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B624438-4340-4333-B2F6-2ADA7A93006C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Debug|x64.Build.0 = Debug|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Debug|x86.Build.0 = Debug|Any CPU
{7B624438-4340-4333-B2F6-2ADA7A93006C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B624438-4340-4333-B2F6-2ADA7A93006C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Release|x64.ActiveCfg = Release|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Release|x64.Build.0 = Release|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Release|x86.ActiveCfg = Release|Any CPU
+ {7B624438-4340-4333-B2F6-2ADA7A93006C}.Release|x86.Build.0 = Release|Any CPU
{F60B3806-CC22-470E-80EE-2E480912CE4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F60B3806-CC22-470E-80EE-2E480912CE4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Debug|x64.Build.0 = Debug|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Debug|x86.Build.0 = Debug|Any CPU
{F60B3806-CC22-470E-80EE-2E480912CE4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F60B3806-CC22-470E-80EE-2E480912CE4D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Release|x64.ActiveCfg = Release|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Release|x64.Build.0 = Release|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Release|x86.ActiveCfg = Release|Any CPU
+ {F60B3806-CC22-470E-80EE-2E480912CE4D}.Release|x86.Build.0 = Release|Any CPU
{E264ABD1-EDC9-4E8E-B828-9CA239792051}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E264ABD1-EDC9-4E8E-B828-9CA239792051}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Debug|x64.Build.0 = Debug|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Debug|x86.Build.0 = Debug|Any CPU
{E264ABD1-EDC9-4E8E-B828-9CA239792051}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E264ABD1-EDC9-4E8E-B828-9CA239792051}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Release|x64.ActiveCfg = Release|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Release|x64.Build.0 = Release|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Release|x86.ActiveCfg = Release|Any CPU
+ {E264ABD1-EDC9-4E8E-B828-9CA239792051}.Release|x86.Build.0 = Release|Any CPU
{44E0145A-684C-466D-8258-171AF9751D95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44E0145A-684C-466D-8258-171AF9751D95}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Debug|x64.Build.0 = Debug|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Debug|x86.Build.0 = Debug|Any CPU
{44E0145A-684C-466D-8258-171AF9751D95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44E0145A-684C-466D-8258-171AF9751D95}.Release|Any CPU.Build.0 = Release|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Release|x64.ActiveCfg = Release|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Release|x64.Build.0 = Release|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Release|x86.ActiveCfg = Release|Any CPU
+ {44E0145A-684C-466D-8258-171AF9751D95}.Release|x86.Build.0 = Release|Any CPU
{112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Debug|x64.Build.0 = Debug|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Debug|x86.Build.0 = Debug|Any CPU
{112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Release|x64.ActiveCfg = Release|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Release|x64.Build.0 = Release|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Release|x86.ActiveCfg = Release|Any CPU
+ {112C5C1D-F0A6-4068-A9EB-6047CA1F5CDF}.Release|x86.Build.0 = Release|Any CPU
{51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Debug|x64.Build.0 = Debug|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Debug|x86.Build.0 = Debug|Any CPU
{51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Release|Any CPU.Build.0 = Release|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Release|x64.ActiveCfg = Release|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Release|x64.Build.0 = Release|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Release|x86.ActiveCfg = Release|Any CPU
+ {51F052FC-2DCB-48AF-A4D3-5C42C8C5F713}.Release|x86.Build.0 = Release|Any CPU
{A350D217-DC32-4537-8A9C-167B560CAF75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A350D217-DC32-4537-8A9C-167B560CAF75}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Debug|x64.Build.0 = Debug|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Debug|x86.Build.0 = Debug|Any CPU
{A350D217-DC32-4537-8A9C-167B560CAF75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A350D217-DC32-4537-8A9C-167B560CAF75}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Release|x64.ActiveCfg = Release|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Release|x64.Build.0 = Release|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Release|x86.ActiveCfg = Release|Any CPU
+ {A350D217-DC32-4537-8A9C-167B560CAF75}.Release|x86.Build.0 = Release|Any CPU
{9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Debug|x64.Build.0 = Debug|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Debug|x86.Build.0 = Debug|Any CPU
{9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Release|x64.ActiveCfg = Release|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Release|x64.Build.0 = Release|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Release|x86.ActiveCfg = Release|Any CPU
+ {9A433C22-4811-4AD9-99C1-3DF85D9FB54B}.Release|x86.Build.0 = Release|Any CPU
{81EA8494-176C-4178-A1C3-6FA3B1222B74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81EA8494-176C-4178-A1C3-6FA3B1222B74}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Debug|x64.Build.0 = Debug|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Debug|x86.Build.0 = Debug|Any CPU
{81EA8494-176C-4178-A1C3-6FA3B1222B74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81EA8494-176C-4178-A1C3-6FA3B1222B74}.Release|Any CPU.Build.0 = Release|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Release|x64.ActiveCfg = Release|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Release|x64.Build.0 = Release|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Release|x86.ActiveCfg = Release|Any CPU
+ {81EA8494-176C-4178-A1C3-6FA3B1222B74}.Release|x86.Build.0 = Release|Any CPU
{085F3A30-A788-48D6-8067-74D71C29A941}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{085F3A30-A788-48D6-8067-74D71C29A941}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Debug|x64.Build.0 = Debug|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Debug|x86.Build.0 = Debug|Any CPU
{085F3A30-A788-48D6-8067-74D71C29A941}.Release|Any CPU.ActiveCfg = Release|Any CPU
{085F3A30-A788-48D6-8067-74D71C29A941}.Release|Any CPU.Build.0 = Release|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Release|x64.ActiveCfg = Release|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Release|x64.Build.0 = Release|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Release|x86.ActiveCfg = Release|Any CPU
+ {085F3A30-A788-48D6-8067-74D71C29A941}.Release|x86.Build.0 = Release|Any CPU
{6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Debug|x64.Build.0 = Debug|Any CPU
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Debug|x86.Build.0 = Debug|Any CPU
{6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Release|Any CPU.Build.0 = Release|Any CPU
-
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Release|x64.ActiveCfg = Release|Any CPU
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Release|x64.Build.0 = Release|Any CPU
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Release|x86.ActiveCfg = Release|Any CPU
+ {6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716}.Release|x86.Build.0 = Release|Any CPU
{345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Debug|x64.Build.0 = Debug|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Debug|x86.Build.0 = Debug|Any CPU
{345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Release|x64.ActiveCfg = Release|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Release|x64.Build.0 = Release|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Release|x86.ActiveCfg = Release|Any CPU
+ {345DA0D1-C762-49EF-9953-6F4D57CB7FC7}.Release|x86.Build.0 = Release|Any CPU
{C95689B5-C0A1-4C1F-9E97-369D3D397930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C95689B5-C0A1-4C1F-9E97-369D3D397930}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Debug|x64.Build.0 = Debug|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Debug|x86.Build.0 = Debug|Any CPU
{C95689B5-C0A1-4C1F-9E97-369D3D397930}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C95689B5-C0A1-4C1F-9E97-369D3D397930}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Release|x64.ActiveCfg = Release|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Release|x64.Build.0 = Release|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Release|x86.ActiveCfg = Release|Any CPU
+ {C95689B5-C0A1-4C1F-9E97-369D3D397930}.Release|x86.Build.0 = Release|Any CPU
{8551C158-60B4-4594-8B1D-5BE851F90EE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8551C158-60B4-4594-8B1D-5BE851F90EE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Debug|x64.Build.0 = Debug|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Debug|x86.Build.0 = Debug|Any CPU
{8551C158-60B4-4594-8B1D-5BE851F90EE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8551C158-60B4-4594-8B1D-5BE851F90EE4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Release|x64.ActiveCfg = Release|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Release|x64.Build.0 = Release|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Release|x86.ActiveCfg = Release|Any CPU
+ {8551C158-60B4-4594-8B1D-5BE851F90EE4}.Release|x86.Build.0 = Release|Any CPU
{874C7405-ED8D-477D-9362-0C69CF56F213}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{874C7405-ED8D-477D-9362-0C69CF56F213}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Debug|x64.Build.0 = Debug|Any CPU
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Debug|x86.Build.0 = Debug|Any CPU
{874C7405-ED8D-477D-9362-0C69CF56F213}.Release|Any CPU.ActiveCfg = Release|Any CPU
{874C7405-ED8D-477D-9362-0C69CF56F213}.Release|Any CPU.Build.0 = Release|Any CPU
-
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Release|x64.ActiveCfg = Release|Any CPU
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Release|x64.Build.0 = Release|Any CPU
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Release|x86.ActiveCfg = Release|Any CPU
+ {874C7405-ED8D-477D-9362-0C69CF56F213}.Release|x86.Build.0 = Release|Any CPU
{05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Debug|x64.Build.0 = Debug|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Debug|x86.Build.0 = Debug|Any CPU
{05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Release|x64.ActiveCfg = Release|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Release|x64.Build.0 = Release|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Release|x86.ActiveCfg = Release|Any CPU
+ {05E93A3E-CFA0-4980-8EE5-CD25C7ED766D}.Release|x86.Build.0 = Release|Any CPU
{AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Debug|x64.Build.0 = Debug|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Debug|x86.Build.0 = Debug|Any CPU
{AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Release|x64.ActiveCfg = Release|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Release|x64.Build.0 = Release|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Release|x86.ActiveCfg = Release|Any CPU
+ {AAFC86EB-49D7-4FD8-8C79-C42C129EB75A}.Release|x86.Build.0 = Release|Any CPU
{98A11016-DD41-4848-A848-51D703951A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98A11016-DD41-4848-A848-51D703951A91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Debug|x64.Build.0 = Debug|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Debug|x86.Build.0 = Debug|Any CPU
{98A11016-DD41-4848-A848-51D703951A91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98A11016-DD41-4848-A848-51D703951A91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Release|x64.ActiveCfg = Release|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Release|x64.Build.0 = Release|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Release|x86.ActiveCfg = Release|Any CPU
+ {98A11016-DD41-4848-A848-51D703951A91}.Release|x86.Build.0 = Release|Any CPU
{106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Debug|x64.Build.0 = Debug|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Debug|x86.Build.0 = Debug|Any CPU
{106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Release|Any CPU.Build.0 = Release|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Release|x64.ActiveCfg = Release|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Release|x64.Build.0 = Release|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Release|x86.ActiveCfg = Release|Any CPU
+ {106FBE12-6FF7-40DC-9B3C-E5F67F335B32}.Release|x86.Build.0 = Release|Any CPU
{565A9701-3D9C-49F8-86B7-D256A1D9E074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{565A9701-3D9C-49F8-86B7-D256A1D9E074}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Debug|x64.Build.0 = Debug|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Debug|x86.Build.0 = Debug|Any CPU
{565A9701-3D9C-49F8-86B7-D256A1D9E074}.Release|Any CPU.ActiveCfg = Release|Any CPU
{565A9701-3D9C-49F8-86B7-D256A1D9E074}.Release|Any CPU.Build.0 = Release|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Release|x64.ActiveCfg = Release|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Release|x64.Build.0 = Release|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Release|x86.ActiveCfg = Release|Any CPU
+ {565A9701-3D9C-49F8-86B7-D256A1D9E074}.Release|x86.Build.0 = Release|Any CPU
{EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Debug|x64.Build.0 = Debug|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Debug|x86.Build.0 = Debug|Any CPU
{EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|x64.ActiveCfg = Release|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|x64.Build.0 = Release|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|x86.ActiveCfg = Release|Any CPU
+ {EAF4A3B8-5CD0-48ED-B848-0EA6D451B8D3}.Release|x86.Build.0 = Release|Any CPU
{C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|x64.Build.0 = Debug|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Debug|x86.Build.0 = Debug|Any CPU
{C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|x64.ActiveCfg = Release|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|x64.Build.0 = Release|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|x86.ActiveCfg = Release|Any CPU
+ {C99A0F7C-9477-4985-90F6-8EED38ECAC10}.Release|x86.Build.0 = Release|Any CPU
{D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|x64.Build.0 = Debug|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Debug|x86.Build.0 = Debug|Any CPU
{D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|x64.ActiveCfg = Release|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|x64.Build.0 = Release|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|x86.ActiveCfg = Release|Any CPU
+ {D95519CA-BD27-45AE-B83B-3FB02E7AE445}.Release|x86.Build.0 = Release|Any CPU
{0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|x64.Build.0 = Debug|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Debug|x86.Build.0 = Debug|Any CPU
{0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|x64.ActiveCfg = Release|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|x64.Build.0 = Release|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|x86.ActiveCfg = Release|Any CPU
+ {0633CB2B-3508-48E5-A8C2-427A83A5CA6E}.Release|x86.Build.0 = Release|Any CPU
{63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|x64.Build.0 = Debug|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Debug|x86.Build.0 = Debug|Any CPU
{63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|Any CPU.Build.0 = Release|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|x64.ActiveCfg = Release|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|x64.Build.0 = Release|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|x86.ActiveCfg = Release|Any CPU
+ {63DC05A0-5B16-45A4-BDE5-90DD2E200507}.Release|x86.Build.0 = Release|Any CPU
{38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|x64.Build.0 = Debug|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Debug|x86.Build.0 = Debug|Any CPU
{38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|Any CPU.Build.0 = Release|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|x64.ActiveCfg = Release|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|x64.Build.0 = Release|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|x86.ActiveCfg = Release|Any CPU
+ {38C8C3B0-163D-4B7B-86A2-3EFFBC165E99}.Release|x86.Build.0 = Release|Any CPU
{1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|x64.Build.0 = Debug|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Debug|x86.Build.0 = Debug|Any CPU
{1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|x64.ActiveCfg = Release|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|x64.Build.0 = Release|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|x86.ActiveCfg = Release|Any CPU
+ {1AF980DF-DEEA-4E5D-9001-6EC67EB96AD1}.Release|x86.Build.0 = Release|Any CPU
{3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Debug|x64.Build.0 = Debug|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Debug|x86.Build.0 = Debug|Any CPU
{3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Release|x64.ActiveCfg = Release|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Release|x64.Build.0 = Release|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Release|x86.ActiveCfg = Release|Any CPU
+ {3F159C49-3DE7-42F5-AF14-E64C03AF19E8}.Release|x86.Build.0 = Release|Any CPU
{D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Debug|x64.Build.0 = Debug|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Debug|x86.Build.0 = Debug|Any CPU
{D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Release|x64.ActiveCfg = Release|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Release|x64.Build.0 = Release|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Release|x86.ActiveCfg = Release|Any CPU
+ {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F}.Release|x86.Build.0 = Release|Any CPU
{7485EAED-F81C-4119-BABC-E009A21ACE46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7485EAED-F81C-4119-BABC-E009A21ACE46}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Debug|x64.Build.0 = Debug|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Debug|x86.Build.0 = Debug|Any CPU
{7485EAED-F81C-4119-BABC-E009A21ACE46}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7485EAED-F81C-4119-BABC-E009A21ACE46}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Release|x64.ActiveCfg = Release|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Release|x64.Build.0 = Release|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Release|x86.ActiveCfg = Release|Any CPU
+ {7485EAED-F81C-4119-BABC-E009A21ACE46}.Release|x86.Build.0 = Release|Any CPU
{43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Debug|x64.Build.0 = Debug|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Debug|x86.Build.0 = Debug|Any CPU
{43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Release|Any CPU.Build.0 = Release|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Release|x64.ActiveCfg = Release|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Release|x64.Build.0 = Release|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Release|x86.ActiveCfg = Release|Any CPU
+ {43C5E98B-5EC4-9F2B-2676-8F1E34969855}.Release|x86.Build.0 = Release|Any CPU
{6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Debug|x64.Build.0 = Debug|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Debug|x86.Build.0 = Debug|Any CPU
{6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Release|x64.ActiveCfg = Release|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Release|x64.Build.0 = Release|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Release|x86.ActiveCfg = Release|Any CPU
+ {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6}.Release|x86.Build.0 = Release|Any CPU
{9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Debug|x64.Build.0 = Debug|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Debug|x86.Build.0 = Debug|Any CPU
{9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Release|x64.ActiveCfg = Release|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Release|x64.Build.0 = Release|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Release|x86.ActiveCfg = Release|Any CPU
+ {9D601495-FDBA-C852-4ACB-EC54EDC9B3E5}.Release|x86.Build.0 = Release|Any CPU
{F578CA07-E74F-4F47-9203-C67777D9BB78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F578CA07-E74F-4F47-9203-C67777D9BB78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Debug|x64.Build.0 = Debug|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Debug|x86.Build.0 = Debug|Any CPU
{F578CA07-E74F-4F47-9203-C67777D9BB78}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F578CA07-E74F-4F47-9203-C67777D9BB78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Release|x64.ActiveCfg = Release|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Release|x64.Build.0 = Release|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Release|x86.ActiveCfg = Release|Any CPU
+ {F578CA07-E74F-4F47-9203-C67777D9BB78}.Release|x86.Build.0 = Release|Any CPU
{E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Debug|x64.Build.0 = Debug|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Debug|x86.Build.0 = Debug|Any CPU
{E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Release|x64.ActiveCfg = Release|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Release|x64.Build.0 = Release|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Release|x86.ActiveCfg = Release|Any CPU
+ {E10920BB-6409-41BB-9A9D-813BC37CC3C0}.Release|x86.Build.0 = Release|Any CPU
{B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|x64.Build.0 = Debug|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|x86.Build.0 = Debug|Any CPU
{B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|x64.ActiveCfg = Release|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|x64.Build.0 = Release|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|x86.ActiveCfg = Release|Any CPU
+ {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Release|x86.Build.0 = Release|Any CPU
{11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|x64.Build.0 = Debug|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|x86.Build.0 = Debug|Any CPU
{11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|x64.ActiveCfg = Release|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|x64.Build.0 = Release|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|x86.ActiveCfg = Release|Any CPU
+ {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|x86.Build.0 = Release|Any CPU
{2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|x64.Build.0 = Debug|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Debug|x86.Build.0 = Debug|Any CPU
{2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|x64.ActiveCfg = Release|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|x64.Build.0 = Release|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|x86.ActiveCfg = Release|Any CPU
+ {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|x86.Build.0 = Release|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Debug|x64.Build.0 = Debug|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Debug|x86.Build.0 = Debug|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Release|x64.ActiveCfg = Release|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Release|x64.Build.0 = Release|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Release|x86.ActiveCfg = Release|Any CPU
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671}.Release|x86.Build.0 = Release|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Debug|x64.Build.0 = Debug|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Debug|x86.Build.0 = Debug|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Release|x64.ActiveCfg = Release|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Release|x64.Build.0 = Release|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Release|x86.ActiveCfg = Release|Any CPU
+ {A30F8E57-AF18-40EC-B130-140585246CC7}.Release|x86.Build.0 = Release|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Debug|x64.Build.0 = Debug|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Debug|x86.Build.0 = Debug|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Release|x64.ActiveCfg = Release|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Release|x64.Build.0 = Release|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Release|x86.ActiveCfg = Release|Any CPU
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -378,14 +806,12 @@ Global
{81EA8494-176C-4178-A1C3-6FA3B1222B74} = {39EAAA32-53A8-4641-873C-976FD5963360}
{085F3A30-A788-48D6-8067-74D71C29A941} = {39EAAA32-53A8-4641-873C-976FD5963360}
{6FCC8A6C-A172-4AAF-A0FC-66C3BD9E8716} = {39EAAA32-53A8-4641-873C-976FD5963360}
-
{6FF2EDB6-D1B8-4EE0-B1F0-2BCE66972E39} = {4429C078-35C8-4E2B-9C7B-F0C619741B67}
{345DA0D1-C762-49EF-9953-6F4D57CB7FC7} = {6FF2EDB6-D1B8-4EE0-B1F0-2BCE66972E39}
{C95689B5-C0A1-4C1F-9E97-369D3D397930} = {6FF2EDB6-D1B8-4EE0-B1F0-2BCE66972E39}
{8551C158-60B4-4594-8B1D-5BE851F90EE4} = {6FF2EDB6-D1B8-4EE0-B1F0-2BCE66972E39}
{874C7405-ED8D-477D-9362-0C69CF56F213} = {6FF2EDB6-D1B8-4EE0-B1F0-2BCE66972E39}
{74979310-8A92-47DC-B5CA-EFA7970E1202} = {4429C078-35C8-4E2B-9C7B-F0C619741B67}
-
{05E93A3E-CFA0-4980-8EE5-CD25C7ED766D} = {D859B39C-9106-4D3D-8C57-11B15FA8106B}
{AAFC86EB-49D7-4FD8-8C79-C42C129EB75A} = {5FBEAD92-9234-4824-9320-2052D236C9CD}
{98A11016-DD41-4848-A848-51D703951A91} = {5FBEAD92-9234-4824-9320-2052D236C9CD}
@@ -414,6 +840,9 @@ Global
{11497EB7-B702-B537-3CBE-BA2F4F85F313} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD}
{A65C33EA-4F2E-DE85-7501-4389A2100813} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD}
{2B6F24A0-4569-E8A2-81B4-3925FA4F0320} = {A65C33EA-4F2E-DE85-7501-4389A2100813}
+ {4E74F2DB-3BA5-4390-8FBF-C58F57601671} = {BC1690DE-FD9E-72EA-CAED-A2B9A3D6B335}
+ {A30F8E57-AF18-40EC-B130-140585246CC7} = {BC1690DE-FD9E-72EA-CAED-A2B9A3D6B335}
+ {1DB37AC5-18FE-4535-816C-827B4D3DCB96} = {BC1690DE-FD9E-72EA-CAED-A2B9A3D6B335}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08502818-E8E1-4A91-A51C-4C8C8D4FF9CA}
From 76079dcf651fbcf04250aefb7afc87da735fea86 Mon Sep 17 00:00:00 2001
From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com>
Date: Tue, 25 Nov 2025 16:52:25 -0600
Subject: [PATCH 16/16] Update tests.md
---
steering_docs/dotnet-tech/tests.md | 237 ++++++++++-------------------
1 file changed, 77 insertions(+), 160 deletions(-)
diff --git a/steering_docs/dotnet-tech/tests.md b/steering_docs/dotnet-tech/tests.md
index b66e437a5ae..5d63bf90a6a 100644
--- a/steering_docs/dotnet-tech/tests.md
+++ b/steering_docs/dotnet-tech/tests.md
@@ -21,12 +21,12 @@ read_documentation("https://docs.aws.amazon.com/[service]/latest/[relevant-page]
**FAILURE TO COMPLETE KNOWLEDGE BASE CONSULTATION WILL RESULT IN INCORRECT CODE STRUCTURE**
## Purpose
-Generate integration test suites using MSTest framework to validate complete scenario workflows against real AWS services.
+Generate integration test suites using xUnit framework to validate complete scenario workflows against real AWS services.
## Requirements
- **Integration Tests Only**: Focus on end-to-end scenario testing, not unit tests
- **Real AWS Services**: Tests run against actual AWS infrastructure
-- **Proper Attributes**: Use MSTest attributes for test categorization
+- **Proper Attributes**: Use xUnit attributes for test categorization
- **Async Testing**: Use async Task for async test methods
- **Resource Cleanup**: Ensure proper cleanup of AWS resources after tests
@@ -40,7 +40,7 @@ dotnetv4/{Service}/Tests/
**CRITICAL:**
- ✅ **Integration tests ONLY** - no separate unit tests for wrapper methods
- ✅ **All tests in one project** - no separate IntegrationTests project
-- ✅ **Use MSTest framework** - not xUnit
+- ✅ **Use xUnit framework** - not MSTest
- ✅ **Test against real AWS** - not mocks
## Test Project Setup
@@ -59,10 +59,16 @@ dotnetv4/{Service}/Tests/
-
-
-
-
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
@@ -81,7 +87,7 @@ dotnetv4/{Service}/Tests/
```
**Key Changes:**
-- ✅ Use MSTest packages instead of xUnit
+- ✅ Use xUnit packages (not MSTest)
- ✅ Target .NET 8.0 with latest language version
- ✅ Use latest AWS SDK package versions
- ✅ Reference Actions project only (no Scenarios reference needed)
@@ -92,12 +98,11 @@ dotnetv4/{Service}/Tests/
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-using System;
using System.Threading.Tasks;
using Amazon.{Service};
using Amazon.{ServiceDataAPI};
-using Microsoft.VisualStudio.TestTools.UnitTesting;
using {Service}Actions;
+using Xunit;
namespace {Service}Tests;
@@ -105,163 +110,74 @@ namespace {Service}Tests;
/// Integration tests for Amazon {Service} operations.
/// These tests require actual AWS credentials and will create real AWS resources.
///
-[TestClass]
public class {Service}IntegrationTests
{
- private static {Service}Wrapper? _{service}Wrapper;
- private static string? _testResourceIdentifier;
- private const string TestDatabaseName = "dev";
- private const string TestUsername = "testuser";
- private const string TestPassword = "TestPassword123!";
-
- [ClassInitialize]
- public static void ClassInitialize(TestContext context)
+ ///
+ /// Verifies the scenario with an integration test. No exceptions should be thrown.
+ ///
+ /// Async task.
+ [Fact]
+ [Trait("Category", "Integration")]
+ public async Task TestScenarioIntegration()
{
- // Initialize clients
- var {service}Client = new Amazon{Service}Client();
- var {service}DataClient = new Amazon{ServiceDataAPI}Client();
- _{service}Wrapper = new {Service}Wrapper({service}Client, {service}DataClient);
-
- // Generate unique resource identifier
- _testResourceIdentifier = $"test-resource-{DateTime.Now:yyyyMMddHHmmss}";
-
- Console.WriteLine($"Integration tests will use resource: {_testResourceIdentifier}");
- }
+ // Arrange
+ {Service}Basics.{Service}Basics.IsInteractive = false;
- [ClassCleanup]
- public static async Task ClassCleanup()
- {
- // Clean up any remaining test resources
- if (_{service}Wrapper != null && !string.IsNullOrEmpty(_testResourceIdentifier))
- {
- try
- {
- Console.WriteLine($"Cleaning up test resource: {_testResourceIdentifier}");
- await _{service}Wrapper.DeleteResourceAsync(_testResourceIdentifier);
- Console.WriteLine("Test resource cleanup initiated.");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Warning: Failed to cleanup test resource: {ex.Message}");
- }
- }
+ // Act
+ {Service}Basics.{Service}Basics.Wrapper = new {Service}Wrapper(
+ new Amazon{Service}Client(),
+ new Amazon{ServiceDataAPI}Client());
+
+ await {Service}Basics.{Service}Basics.RunScenarioAsync();
+
+ // Assert - if we get here without exceptions, the test passes
}
}
```
**Key Changes:**
- ✅ Use file-scoped namespaces
-- ✅ Use MSTest attributes ([TestClass], [TestMethod], [ClassInitialize], [ClassCleanup])
+- ✅ Use xUnit attributes ([Fact], [Trait])
- ✅ No mocking - test against real AWS services
-- ✅ Include proper resource cleanup in ClassCleanup
+- ✅ Test runs the complete scenario end-to-end
+- ✅ Set IsInteractive = false for non-interactive test execution
## Integration Test Patterns
-### Basic Integration Test
-```csharp
-[TestMethod]
-[TestCategory("Integration")]
-public async Task DescribeResources_Integration_ReturnsResourceList()
-{
- // Act
- var resources = await _{service}Wrapper!.DescribeResourcesAsync();
+### Integration Test Pattern
+The integration test should run the complete scenario end-to-end:
- // Assert
- Assert.IsNotNull(resources);
- // Note: We don't assert specific count since other resources might exist
- Console.WriteLine($"Found {resources.Count} existing resources.");
-}
-```
-
-### Full Workflow Integration Test
```csharp
-[TestMethod]
-[TestCategory("Integration")]
-[TestCategory("LongRunning")]
-public async Task {Service}FullWorkflow_Integration_CompletesSuccessfully()
-{
- // This test runs the complete {Service} workflow
- // Note: This test can take 10-15 minutes to complete
-
- try
- {
- Console.WriteLine("Starting {Service} full workflow integration test...");
-
- // Step 1: Create resource
- Console.WriteLine($"Creating resource: {_testResourceIdentifier}");
- var createdResource = await _{service}Wrapper!.CreateResourceAsync(
- _testResourceIdentifier!,
- TestDatabaseName,
- TestUsername,
- TestPassword);
-
- Assert.IsNotNull(createdResource);
- Assert.AreEqual(_testResourceIdentifier, createdResource.Identifier);
- Console.WriteLine("Resource creation initiated successfully.");
-
- // Step 2: Wait for resource to become available
- Console.WriteLine("Waiting for resource to become available...");
- await WaitForResourceAvailable(_testResourceIdentifier!, TimeSpan.FromMinutes(20));
-
- // Step 3: Perform operations
- Console.WriteLine("Performing operations...");
- var result = await _{service}Wrapper.PerformOperationAsync(_testResourceIdentifier!);
- Assert.IsNotNull(result);
- Console.WriteLine("Operations completed successfully.");
-
- Console.WriteLine("Full workflow integration test completed successfully!");
- }
- finally
- {
- // Clean up - Delete resource
- if (!string.IsNullOrEmpty(_testResourceIdentifier))
- {
- Console.WriteLine($"Deleting test resource: {_testResourceIdentifier}");
- try
- {
- var deletedResource = await _{service}Wrapper!.DeleteResourceAsync(_testResourceIdentifier);
- Assert.IsNotNull(deletedResource);
- Console.WriteLine("Resource deletion initiated successfully.");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Failed to delete resource: {ex.Message}");
- throw;
- }
- }
- }
-}
-
///
-/// Wait for a resource to become available with timeout.
+/// Verifies the scenario with an integration test. No exceptions should be thrown.
///
-/// The resource identifier.
-/// Maximum time to wait.
-private async Task WaitForResourceAvailable(string resourceIdentifier, TimeSpan timeout)
+/// Async task.
+[Fact]
+[Trait("Category", "Integration")]
+public async Task TestScenarioIntegration()
{
- var startTime = DateTime.UtcNow;
- var endTime = startTime.Add(timeout);
+ // Arrange
+ {Service}Basics.{Service}Basics.IsInteractive = false;
- while (DateTime.UtcNow < endTime)
- {
- var resources = await _{service}Wrapper!.DescribeResourcesAsync(resourceIdentifier);
-
- if (resources.Count > 0 && resources[0].Status == "available")
- {
- Console.WriteLine($"Resource {resourceIdentifier} is now available!");
- return;
- }
-
- var elapsed = DateTime.UtcNow - startTime;
- Console.WriteLine($"Waiting for resource... Elapsed time: {elapsed:mm\\:ss}");
-
- await Task.Delay(TimeSpan.FromSeconds(30)); // Wait 30 seconds between checks
- }
+ // Act
+ {Service}Basics.{Service}Basics.Wrapper = new {Service}Wrapper(
+ new Amazon{Service}Client(),
+ new Amazon{ServiceDataAPI}Client());
- throw new TimeoutException($"Resource {resourceIdentifier} did not become available within {timeout.TotalMinutes} minutes.");
+ await {Service}Basics.{Service}Basics.RunScenarioAsync();
+
+ // Assert - if we get here without exceptions, the test passes
}
```
+**Key Points:**
+- ✅ Use `[Fact]` attribute for test methods
+- ✅ Use `[Trait("Category", "Integration")]` for categorization
+- ✅ Set `IsInteractive = false` to run without user input
+- ✅ Create real AWS clients (not mocked)
+- ✅ Call the scenario's `RunScenarioAsync()` method
+- ✅ Test passes if no exceptions are thrown
+
## Test Execution Commands
### All Tests
@@ -281,35 +197,36 @@ dotnet test dotnetv4/{Service}/Tests/{Service}Tests.csproj --filter "TestCategor
## Test Requirements Checklist
- ✅ **Test project file created** with proper dependencies
-- ✅ **MSTest framework** (not xUnit)
+- ✅ **xUnit framework** (not MSTest)
- ✅ **Integration tests only** (no unit tests with mocks)
-- ✅ **Proper MSTest attributes** (`[TestCategory("Integration")]`)
+- ✅ **Proper xUnit attributes** (`[Fact]`, `[Trait("Category", "Integration")]`)
- ✅ **Test against real AWS services** (not mocks)
-- ✅ **Proper resource cleanup** in ClassCleanup method
+- ✅ **Test runs complete scenario** via `RunScenarioAsync()`
- ✅ **Async test methods** using `async Task`
- ✅ **File-scoped namespaces** for modern C# style
## Integration Test Focus
-### Primary Test: Complete Workflow Integration
+### Primary Test: Complete Scenario Integration
The main integration test should:
-- ✅ **Run the entire workflow** from start to finish
+- ✅ **Run the entire scenario** from start to finish via `RunScenarioAsync()`
- ✅ **Test against real AWS services** (not mocks)
-- ✅ **Create and clean up resources** properly
-- ✅ **Handle long-running operations** with appropriate timeouts
-- ✅ **Use TestCategory attributes** for test organization
+- ✅ **Set IsInteractive = false** for non-interactive execution
+- ✅ **Create real AWS clients** (not mocked)
+- ✅ **Use xUnit attributes** (`[Fact]`, `[Trait]`) for test organization
+- ✅ **Pass if no exceptions** are thrown during execution
-### Test Structure Priority
-1. **Full Workflow Integration Test** - Most important, tests complete workflow
-2. **Basic Operation Tests** - Test individual operations against real AWS
-3. **Resource Lifecycle Tests** - Test create, read, update, delete operations
+### Test Structure
+- **Single Integration Test** - Tests the complete scenario workflow
+- **No separate unit tests** - Focus on end-to-end integration only
+- **No mocking** - Use real AWS clients and services
## Common Test Failures to Avoid
- ❌ **Using mocks instead of real AWS services** in integration tests
-- ❌ **Missing MSTest attributes** for integration tests
-- ❌ **Not cleaning up resources** in ClassCleanup
+- ❌ **Missing xUnit attributes** (`[Fact]`, `[Trait]`) for integration tests
+- ❌ **Not setting IsInteractive = false** for non-interactive execution
- ❌ **Forgetting to set AWS region** in test clients
-- ❌ **Not handling long-running operations** with timeouts
- ❌ **Missing proper async/await patterns** in test methods
-- ❌ **Using xUnit instead of MSTest** framework
-- ❌ **Creating separate IntegrationTests project** instead of using Tests project
\ No newline at end of file
+- ❌ **Using MSTest instead of xUnit** framework
+- ❌ **Creating separate IntegrationTests project** instead of using Tests project
+- ❌ **Not referencing the Scenarios project** when testing scenarios
\ No newline at end of file