Skip to content

.NET code example standards

Rachel Hagerman edited this page Jul 26, 2024 · 2 revisions

This document summarizes important points for writing and reviewing code examples written for the AWS .NET SDK in C#. For more information on tools and standards, see the complete list in TCX Code Examples Standards.

General Structure

  • Service folders with examples should follow the common structure that uses folders for Actions, Scenarios, and Tests, with a top level solution file.

  • Scenarios should use .NET Dependency Injection with interfaces to set up AWS services.

          static async Task Main(string[] args)
              {
                  // Set up dependency injection for the Amazon RDS service.
                  using var host = Host.CreateDefaultBuilder(args)
                      .ConfigureLogging(logging =>
                          logging.AddFilter("System", LogLevel.Debug)
                              .AddFilter<DebugLoggerProvider>("Microsoft", LogLevel.Information)
                              .AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Trace))
                      .ConfigureServices((_, services) =>
                          services.AddAWSService<IAmazonRDS>()
                              .AddTransient<RDSWrapper>()
                      )
                      .Build();
          
                  logger = LoggerFactory.Create(builder =>
                  {
                      builder.AddConsole();
                  }).CreateLogger<RDSInstanceScenario>();
          
                  rdsWrapper = host.Services.GetRequiredService<RDSWrapper>();
          
                  Console.WriteLine(sepBar);
                  Console.WriteLine(
                      "Welcome to the Amazon Relational Database Service (Amazon RDS) DB instance scenario example.");
                  Console.WriteLine(sepBar);
                  await BeginScenario();
             }
    
  • Scenarios should include a top-level comment block explaining the steps of the example.

          /*
              Before running this .NET code example, set up your development environment, including your credentials.
          
              This .NET example performs the following tasks:
              1.  Returns a list of the available DB engine families using the DescribeDBEngineVersionsAsync method.
              2.  Selects an engine family and creates a custom DB parameter group using the CreateDBParameterGroupAsync method.
              3.  Gets the parameter groups using the DescribeDBParameterGroupsAsync method.
              4.  Gets parameters in the group using the DescribeDBParameters method.
              5.  Parses and displays parameters in the group.
              6.  Modifies both the auto_increment_offset and auto_increment_increment parameters
                  using the ModifyDBParameterGroupAsync method.
              7.  Gets and displays the updated parameters using the DescribeDBParameters method with a source of "user".
              8.  Gets a list of allowed engine versions using the DescribeDBEngineVersionsAsync method.
              9.  Displays and selects from a list of micro instance classes available for the selected engine and version.
              10. Creates an RDS DB instance that contains a MySql database and uses the parameter group
                  using the CreateDBInstanceAsync method.
              11. Waits for DB instance to be ready using the DescribeDBInstancesAsync method.
              12. Prints out the connection endpoint string for the new DB instance.
              13. Creates a snapshot of the DB instance using the CreateDBSnapshotAsync method.
              14. Waits for DB snapshot to be ready using the DescribeDBSnapshots method.
              15. Deletes the DB instance using the DeleteDBInstanceAsync method.
              16. Waits for DB instance to be deleted using the DescribeDbInstances method.
              17. Deletes the parameter group using the DeleteDBParameterGroupAsync.
              */
    
  • Default configurations should use a settings.json file. Developer-specific configurations can be placed in a settings.local.json or settings.development.json file.

  • Wrapper methods should provide additional context when calling service actions. For example, specify certain parameters and return true or false. Do not use Request/Response classes directly as the parameter and response types.

              // snippet-start:[SESWorkflow.dotnetv3.SESv2Wrapper.CreateContactList]
              /// <summary>
              /// Creates a contact list with the specified name.
              /// </summary>
              /// <param name="contactListName">The name of the contact list.</param>
              /// <returns>True if successful.</returns>
              public async Task<bool> CreateContactListAsync(string contactListName)
              {
                  var request = new CreateContactListRequest
                  {
                      ContactListName = contactListName
                  };
          
                  try
                  {
                      var response = await _sesClient.CreateContactListAsync(request);
                      return response.HttpStatusCode == HttpStatusCode.OK;
                  }
                  catch (AlreadyExistsException ex)
                  {
                      Console.WriteLine($"Contact list with name {contactListName} already exists.");
                      Console.WriteLine(ex.Message);
                      return true;
                  }
                  catch (Exception ex)
                  {
                      Console.WriteLine($"An error occurred while creating the contact list: {ex.Message}");
                  }
                  return false;
              }
              // snippet-end:[SESWorkflow.dotnetv3.SESv2Wrapper.CreateContactList]
    
  • All methods should include full XML doc tags for summary, parameters, and return types.

  • Use file-scoped namespaces to decrease indents, if possible.

  • When using top-level statements, add partial classes if necessary to facilitate testing.

  • When a service is added or updated, add the projects to the top-level solution so it will be included in the build/lint/format and Weathertop tests.

Language Features

  • Use async/await patterns if available. Do not use Task.Result for synchronizing an async method.
  • Prefer var for variable declarations, and common class names such as string over String, etc.
  • Use parameterized values for any query operations.
  • Using statements should be outside the namespace, include no unused imports, and be ordered alphabetically.
  • Prefer file-scoped namespaces to decrease indents in the published snippets.

Testing

  • Use common naming for tests, such as: VerifyAction_ExpectedResult.

  • Integration tests should be marked with [Trait("Category", "Integration")]

  • Use Moq framework for mocking.

  • Use XUnit with ordering for tests.

              /// <summary>
              /// Run the preparation step of the workflow. Should return successful.
              /// </summary>
              /// <returns>Async task.</returns>
              [Fact]
              [Order(1)]
              [Trait("Category", "Unit")]
              public async Task TestPrepareApplication()
              {
                  // Arrange.
                  var mockSesV2Service = new Mock<IAmazonSimpleEmailServiceV2>();
          
                  mockSesV2Service.Setup(client => client.CreateEmailIdentityAsync(
                          It.IsAny<CreateEmailIdentityRequest>(),
                          It.IsAny<CancellationToken>()))
                      .Returns((CreateEmailIdentityResponse r,
                          CancellationToken token) =>
                      {
                          return Task.FromResult(new CreateEmailIdentityResponse()
                          {
                              IdentityType = IdentityType.EMAIL_ADDRESS,
                              HttpStatusCode = HttpStatusCode.OK,
                          });
                      });
          
                  mockSesV2Service.Setup(client => client.CreateContactListAsync(
                          It.IsAny<CreateContactListRequest>(),
                          It.IsAny<CancellationToken>()))
                      .Returns((CreateEmailIdentityResponse r,
                          CancellationToken token) =>
                      {
                          return Task.FromResult(new CreateContactListResponse()
                          {
                              HttpStatusCode = HttpStatusCode.OK,
                          });
                      });
          
                  mockSesV2Service.Setup(client => client.CreateEmailTemplateAsync(
                          It.IsAny<CreateEmailTemplateRequest>(),
                          It.IsAny<CancellationToken>()))
                      .Returns((CreateEmailTemplateResponse r,
                          CancellationToken token) =>
                      {
                          return Task.FromResult(new CreateEmailTemplateResponse()
                          {
                              HttpStatusCode = HttpStatusCode.OK,
                          });
                      });
          
                  var sESv2Wrapper = new SESv2Wrapper(mockSesV2Service.Object);
          
                  NewsletterWorkflow._sesv2Wrapper = sESv2Wrapper;
                  NewsletterWorkflow._verifiedEmail = "test@example.com";
          
                  // Act.
                  var verifiedEmail = await NewsletterWorkflow.PrepareApplication();
          
                  // Assert.
                  Assert.Equal(NewsletterWorkflow._verifiedEmail, verifiedEmail);
              }
    

Metadata Snippet Contents

  • Metadata for Action examples should contain at minimum the following snippets.
    • A snippet to show the action itself within context.
    • If more than one variation of the Action is included, use descriptions in the metadata to explain the differences.
  • Metadata for Scenario examples can contain the entire wrapper and scenario code, but should include descriptions for both.
Clone this wiki locally