From b8064bb75628aaa54e4e2317f7d1bd116b5c7dc6 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 12:01:05 -0400 Subject: [PATCH 01/21] Add CreatePresignedPost examples for dotnetv4 --- dotnetv4/DotNetV4Examples.sln | 28 +- dotnetv4/S3/Actions/CreatePresignedPost.cs | 95 +++++++ .../CreatePresignedPostWithConditions.cs | 111 ++++++++ .../CreatePresignedPostWithFilename.cs | 102 +++++++ .../CreatePresignedPostWithMetadata.cs | 109 ++++++++ dotnetv4/S3/Actions/PresignedPostUtils.cs | 25 ++ dotnetv4/S3/Actions/S3Actions.csproj | 21 ++ dotnetv4/S3/Actions/S3Wrapper.cs | 258 ++++++++++++++++++ dotnetv4/S3/Actions/Usings.cs | 16 ++ dotnetv4/S3/README.md | 179 ++++++++++++ dotnetv4/S3/S3Examples.sln | 40 +++ .../CreatePresignedPostScenario.cs | 253 +++++++++++++++++ .../CreatePresignedPostScenario.csproj | 21 ++ .../S3_CreatePresignedPost/Program.cs | 46 ++++ .../S3_CreatePresignedPost/UiMethods.cs | 50 ++++ .../S3_CreatePresignedPost/Usings.cs | 17 ++ dotnetv4/S3/Tests/S3Tests.csproj | 31 +++ dotnetv4/S3/Tests/S3WrapperTests.cs | 77 ++++++ dotnetv4/S3/Tests/Usings.cs | 9 + 19 files changed, 1486 insertions(+), 2 deletions(-) create mode 100644 dotnetv4/S3/Actions/CreatePresignedPost.cs create mode 100644 dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs create mode 100644 dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs create mode 100644 dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs create mode 100644 dotnetv4/S3/Actions/PresignedPostUtils.cs create mode 100644 dotnetv4/S3/Actions/S3Actions.csproj create mode 100644 dotnetv4/S3/Actions/S3Wrapper.cs create mode 100644 dotnetv4/S3/Actions/Usings.cs create mode 100644 dotnetv4/S3/README.md create mode 100644 dotnetv4/S3/S3Examples.sln create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.csproj create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs create mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs create mode 100644 dotnetv4/S3/Tests/S3Tests.csproj create mode 100644 dotnetv4/S3/Tests/S3WrapperTests.cs create mode 100644 dotnetv4/S3/Tests/Usings.cs diff --git a/dotnetv4/DotNetV4Examples.sln b/dotnetv4/DotNetV4Examples.sln index 9c3a74c6cc2..2e0f7d6fbf3 100644 --- a/dotnetv4/DotNetV4Examples.sln +++ b/dotnetv4/DotNetV4Examples.sln @@ -149,7 +149,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "DynamoDB\Scenario EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoDBActions", "DynamoDB\Actions\DynamoDBActions.csproj", "{B0F91FE2-6AC5-4FA8-B321-54623A516D4D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "S3", "S3", "{F929DB74-DD0E-B0EF-AA66-D8703D547BBD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Actions", "S3\Actions\S3Actions.csproj", "{C0B05982-E721-6989-AFB6-43D7B540248B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Tests", "S3\Tests\S3Tests.csproj", "{11497EB7-B702-B537-3CBE-BA2F4F85F313}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{A65C33EA-4F2E-DE85-7501-4389A2100813}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreatePresignedPostScenario", "S3\Scenarios\S3_CreatePresignedPost\CreatePresignedPostScenario.csproj", "{8DC31D9E-C744-9F54-F67F-D5A387F37BDC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -365,6 +373,18 @@ Global {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|Any CPU.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 + {C0B05982-E721-6989-AFB6-43D7B540248B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0B05982-E721-6989-AFB6-43D7B540248B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0B05982-E721-6989-AFB6-43D7B540248B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0B05982-E721-6989-AFB6-43D7B540248B}.Release|Any CPU.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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Release|Any CPU.Build.0 = Release|Any CPU + {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -425,6 +445,7 @@ Global {3F159C49-3DE7-42F5-AF14-E64C03AF19E8} = {EE6D1933-1E38-406A-B691-446326310D1F} {D44D50E1-EC65-4A1C-AAA1-C360E4FC563F} = {EE6D1933-1E38-406A-B691-446326310D1F} {7485EAED-F81C-4119-BABC-E009A21ACE46} = {EE6D1933-1E38-406A-B691-446326310D1F} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} {43C5E98B-5EC4-9F2B-2676-8F1E34969855} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {6BE1D9A4-1832-49F5-8682-6DEE4A7D6232} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {6B1F00FF-7F1D-C5D8-A8D3-E0EF2886B8C6} = {6BE1D9A4-1832-49F5-8682-6DEE4A7D6232} @@ -432,7 +453,10 @@ Global {F578CA07-E74F-4F47-9203-C67777D9BB78} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} {E10920BB-6409-41BB-9A9D-813BC37CC3C0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {B0F91FE2-6AC5-4FA8-B321-54623A516D4D} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} - {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + {C0B05982-E721-6989-AFB6-43D7B540248B} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} + {11497EB7-B702-B537-3CBE-BA2F4F85F313} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} + {A65C33EA-4F2E-DE85-7501-4389A2100813} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} + {8DC31D9E-C744-9F54-F67F-D5A387F37BDC} = {A65C33EA-4F2E-DE85-7501-4389A2100813} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08502818-E8E1-4A91-A51C-4C8C8D4FF9CA} diff --git a/dotnetv4/S3/Actions/CreatePresignedPost.cs b/dotnetv4/S3/Actions/CreatePresignedPost.cs new file mode 100644 index 00000000000..8e78565f2b9 --- /dev/null +++ b/dotnetv4/S3/Actions/CreatePresignedPost.cs @@ -0,0 +1,95 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Actions; + +// snippet-start:[S3.dotnetv4.CreatePresignedPost] +/// +/// Demonstrates how to use Amazon Simple Storage Service (Amazon S3) +/// CreatePresignedPost functionality to generate a pre-signed URL for browser-based uploads. +/// +public class CreatePresignedPost +{ + /// + /// Create a basic presigned POST URL. + /// + /// The S3Wrapper instance to use. + /// The logger to use. + /// The name of the bucket where the file will be uploaded. + /// The object key (path) where the file will be stored. + /// A CreatePresignedPostResponse containing the URL and form fields. + public static async Task CreateBasicPresignedPost( + S3Wrapper s3Wrapper, + ILogger logger, + string bucketName, + string objectKey) + { + // Set expiration time (maximum is 7 days from now) + var expiration = DateTime.UtcNow.AddHours(1); // 1 hour expiration + + logger.LogInformation("Creating presigned POST URL for {bucket}/{key} with expiration {expiration}", + bucketName, objectKey, expiration); + + var response = await s3Wrapper.CreatePresignedPostAsync(bucketName, objectKey, expiration); + + logger.LogInformation("Successfully created presigned POST URL: {url}", response.Url); + + return response; + } + + + /// + /// Main method that demonstrates the CreatePresignedPost functionality. + /// + /// Command line arguments. Not used in this example. + /// Async task. + public static async Task Main(string[] args) + { + // Set up dependency injection for Amazon S3 + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + // Get the services + var s3Client = host.Services.GetRequiredService(); + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger(); + + // Create the wrapper instance + var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); + + Console.WriteLine("Amazon S3 CreatePresignedPost Basic Example"); + Console.WriteLine("=========================================="); + + try + { + const string bucketName = "amzn-s3-demo-bucket"; + Console.WriteLine($"Using bucket: {bucketName}"); + Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); + + // Create a simple example object key + string objectKey = "example-upload.txt"; + + // Generate the presigned POST URL + Console.WriteLine("\nCreating a presigned POST URL..."); + var response = await CreateBasicPresignedPost(s3Wrapper, logger, bucketName, objectKey); + + // Display the URL and fields that would be needed in an HTML form + PresignedPostUtils.DisplayPresignedPostFields(response); + + Console.WriteLine("\nExample completed successfully."); + } + catch (AmazonS3Exception ex) + { + Console.WriteLine($"Amazon S3 error: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } +} +// snippet-end:[S3.dotnetv4.CreatePresignedPost] diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs new file mode 100644 index 00000000000..41ffca178c9 --- /dev/null +++ b/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs @@ -0,0 +1,111 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Actions; + +/// +/// Demonstrates how to create Amazon S3 presigned POST URLs with conditions. +/// This example shows how to add restrictions to uploads such as content type and size limits. +/// +public class CreatePresignedPostWithConditions +{ + /// + /// Create a presigned POST URL with conditions to restrict uploads. + /// + /// The S3Wrapper instance to use. + /// The logger to use. + /// The name of the bucket. + /// The object key where the uploaded file will be stored. + /// A CreatePresignedPostResponse containing the URL and form fields. + public static async Task CreateWithConditions( + S3Wrapper s3Wrapper, + ILogger logger, + string bucketName, + string objectKey) + { + var expiration = DateTime.UtcNow.AddHours(1); + + var fields = new Dictionary + { + { "Content-Type", "text/plain" } + }; + + var conditions = new List + { + // File size must be between 1 byte and 1 MB + S3PostCondition.ContentLengthRange(1, 1048576) + }; + + logger.LogInformation("Creating presigned POST URL with conditions for {bucket}/{key}", + bucketName, objectKey); + + var response = await s3Wrapper.CreatePresignedPostWithConditionsAsync( + bucketName, objectKey, expiration, fields, conditions); + + logger.LogInformation("Successfully created presigned POST URL with {fieldCount} fields and {conditionCount} conditions", + fields.Count, conditions.Count); + + return response; + } + + + /// + /// Main method that demonstrates creating and using presigned POST URLs with conditions. + /// + /// Command line arguments. Not used in this example. + /// Async task. + public static async Task Main(string[] args) + { + // Set up dependency injection for Amazon S3 + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + // Get the services + var s3Client = host.Services.GetRequiredService(); + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger(); + + // Create the wrapper instance + var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); + + Console.WriteLine("Amazon S3 CreatePresignedPost with Conditions Example"); + Console.WriteLine("==================================================="); + + try + { + const string bucketName = "amzn-s3-demo-bucket"; + Console.WriteLine($"Using bucket: {bucketName}"); + Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); + + // Create an object key for this example + string objectKey = "conditions-example.txt"; + + // Generate the presigned POST URL with conditions + Console.WriteLine("\nCreating a presigned POST URL with upload restrictions..."); + var response = await CreateWithConditions(s3Wrapper, logger, bucketName, objectKey); + + // Display the URL and fields + Console.WriteLine("\nPresigned POST URL with conditions created successfully:"); + PresignedPostUtils.DisplayPresignedPostFields(response); + + Console.WriteLine("\nThis example adds these restrictions:"); + Console.WriteLine(" • Content-Type must start with 'text/' (enforced by policy)"); + Console.WriteLine(" • File size must be between 1 byte and 1 MB (enforced by policy)"); + Console.WriteLine("\nIf these conditions are not met, the upload will be rejected."); + + Console.WriteLine("\nExample completed successfully."); + } + catch (AmazonS3Exception ex) + { + Console.WriteLine($"Amazon S3 error: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } +} diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs new file mode 100644 index 00000000000..b4c1662fdfa --- /dev/null +++ b/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs @@ -0,0 +1,102 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Actions; + +/// +/// Demonstrates how to create Amazon S3 presigned POST URLs with the ${filename} variable. +/// This example shows how to preserve the original filename when uploading files to S3. +/// +public class CreatePresignedPostWithFilename +{ + /// + /// Create a presigned POST URL with the ${filename} variable to preserve original filenames. + /// + /// The S3Wrapper instance to use. + /// The logger to use. + /// The name of the bucket. + /// The prefix for the key, final key will be prefix + actual filename. + /// A CreatePresignedPostResponse containing the URL and form fields. + public static async Task CreateWithFilename( + S3Wrapper s3Wrapper, + ILogger logger, + string bucketName, + string keyPrefix) + { + var expiration = DateTime.UtcNow.AddHours(1); + + // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename + string objectKey = keyPrefix + "${filename}"; + + logger.LogInformation("Creating presigned POST URL with filename variable for {bucket}/{key}", + bucketName, objectKey); + + var response = await s3Wrapper.CreatePresignedPostAsync(bucketName, objectKey, expiration); + + logger.LogInformation("Successfully created presigned POST URL with filename variable"); + logger.LogInformation("When a file is uploaded, it will be stored at: {prefix} + actual filename", + keyPrefix); + + return response; + } + + + /// + /// Main method that demonstrates creating and using presigned POST URLs with the ${filename} variable. + /// + /// Command line arguments. Not used in this example. + /// Async task. + public static async Task Main(string[] args) + { + // Set up dependency injection for Amazon S3 + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + // Get the services + var s3Client = host.Services.GetRequiredService(); + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger(); + + // Create the wrapper instance + var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); + + Console.WriteLine("Amazon S3 CreatePresignedPost with ${filename} Variable Example"); + Console.WriteLine("========================================================"); + + try + { + const string bucketName = "amzn-s3-demo-bucket"; + Console.WriteLine($"Using bucket: {bucketName}"); + Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); + + // Create a key prefix for this example + string keyPrefix = "uploads/"; + + // Generate the presigned POST URL with filename variable + Console.WriteLine("\nCreating a presigned POST URL that preserves the original filename..."); + var response = await CreateWithFilename(s3Wrapper, logger, bucketName, keyPrefix); + + // Display the URL and fields + Console.WriteLine("\nPresigned POST URL with filename variable created successfully:"); + PresignedPostUtils.DisplayPresignedPostFields(response); + + Console.WriteLine("\nThis example uses ${filename} in the key:"); + Console.WriteLine(" • The uploaded file will be stored with its original name in the 'uploads/' prefix"); + Console.WriteLine(" • For example, if you upload 'document.pdf', it will be stored as 'uploads/document.pdf'"); + + Console.WriteLine("\nExample completed successfully."); + } + catch (AmazonS3Exception ex) + { + Console.WriteLine($"Amazon S3 error: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } +} diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs new file mode 100644 index 00000000000..fc3b9c499e1 --- /dev/null +++ b/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs @@ -0,0 +1,109 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Actions; + +/// +/// Demonstrates how to create an Amazon S3 presigned POST URL with metadata. +/// This example shows how to add custom metadata to objects uploaded via presigned POST URLs. +/// +public class CreatePresignedPostWithMetadata +{ + /// + /// Create a presigned POST URL with metadata. + /// + /// The S3Wrapper instance to use. + /// The logger to use. + /// The name of the bucket. + /// The object key where the uploaded file will be stored. + /// A CreatePresignedPostResponse containing the URL and form fields. + public static async Task CreateWithMetadata( + S3Wrapper s3Wrapper, + ILogger logger, + string bucketName, + string objectKey) + { + var expiration = DateTime.UtcNow.AddHours(1); + + var fields = new Dictionary + { + // Add a custom metadata field + { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, + + // Return HTTP 201 on successful upload + { "success_action_status", "201" } + }; + + logger.LogInformation("Creating presigned POST URL with metadata for {bucket}/{key}", + bucketName, objectKey); + + var response = await s3Wrapper.CreatePresignedPostWithFieldsAsync( + bucketName, objectKey, expiration, fields); + + logger.LogInformation("Successfully created presigned POST URL with {count} custom fields", + fields.Count); + + return response; + } + + + /// + /// Main method that demonstrates creating and using presigned POST URLs with metadata. + /// + /// Command line arguments. Not used in this example. + /// Async task. + public static async Task Main(string[] args) + { + // Set up dependency injection for Amazon S3 + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + // Get the services + var s3Client = host.Services.GetRequiredService(); + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var logger = loggerFactory.CreateLogger(); + + // Create the wrapper instance + var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); + + Console.WriteLine("Amazon S3 CreatePresignedPost with Metadata Example"); + Console.WriteLine("=================================================="); + + try + { + const string bucketName = "amzn-s3-demo-bucket"; + Console.WriteLine($"Using bucket: {bucketName}"); + Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); + + // Create an object key for this example + string objectKey = "metadata-example.txt"; + + // Generate the presigned POST URL with metadata + Console.WriteLine("\nCreating a presigned POST URL with metadata fields..."); + var response = await CreateWithMetadata(s3Wrapper, logger, bucketName, objectKey); + + // Display the URL and fields + Console.WriteLine("\nPresigned POST URL with metadata created successfully:"); + PresignedPostUtils.DisplayPresignedPostFields(response); + + Console.WriteLine("\nThis example adds:"); + Console.WriteLine(" • x-amz-meta-uploaded-by: dotnet-sdk-example - adds custom metadata"); + Console.WriteLine(" • success_action_status: 201 - returns HTTP 201 on successful upload"); + + + Console.WriteLine("\nExample completed successfully."); + } + catch (AmazonS3Exception ex) + { + Console.WriteLine($"Amazon S3 error: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + } +} diff --git a/dotnetv4/S3/Actions/PresignedPostUtils.cs b/dotnetv4/S3/Actions/PresignedPostUtils.cs new file mode 100644 index 00000000000..3827cbdfb45 --- /dev/null +++ b/dotnetv4/S3/Actions/PresignedPostUtils.cs @@ -0,0 +1,25 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Actions; + +/// +/// Utility methods for working with presigned POST URLs. +/// +public static class PresignedPostUtils +{ + /// + /// Display the fields from a presigned POST response. + /// + /// The CreatePresignedPostResponse to display. + public static void DisplayPresignedPostFields(CreatePresignedPostResponse response) + { + Console.WriteLine($"Presigned POST URL: {response.Url}"); + Console.WriteLine("Form fields to include:"); + + foreach (var field in response.Fields) + { + Console.WriteLine($" {field.Key}: {field.Value}"); + } + } +} diff --git a/dotnetv4/S3/Actions/S3Actions.csproj b/dotnetv4/S3/Actions/S3Actions.csproj new file mode 100644 index 00000000000..9c41c00dc76 --- /dev/null +++ b/dotnetv4/S3/Actions/S3Actions.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + + + + S3Actions.CreatePresignedPost + + + + + + + + + + diff --git a/dotnetv4/S3/Actions/S3Wrapper.cs b/dotnetv4/S3/Actions/S3Wrapper.cs new file mode 100644 index 00000000000..1a6bc9408fb --- /dev/null +++ b/dotnetv4/S3/Actions/S3Wrapper.cs @@ -0,0 +1,258 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Actions; + +// snippet-start:[S3.dotnetv4.S3Wrapper] +/// +/// Wrapper methods for common Amazon Simple Storage Service (Amazon S3) +/// operations. +/// +public class S3Wrapper +{ + private readonly IAmazonS3 _s3Client; + private readonly ILogger _logger; + + /// + /// Constructor for the wrapper class. + /// + /// The injected S3 client. + /// The injected logger for use with this class. + public S3Wrapper(IAmazonS3 s3Client, ILogger logger) + { + _s3Client = s3Client; + _logger = logger; + } + + /// + /// Create a bucket and wait until it's ready to use. + /// + /// The name of the bucket to create. + /// The name of the newly created bucket. + public async Task CreateBucketAsync(string bucketName) + { + _logger.LogInformation("Creating bucket {bucket}", bucketName); + + var request = new PutBucketRequest + { + BucketName = bucketName + }; + + var response = await _s3Client.PutBucketAsync(request); + + _logger.LogInformation("Created bucket {bucket} with status {status}", + bucketName, response.HttpStatusCode); + + // Wait for the bucket to be available + var exist = await Amazon.S3.Util.AmazonS3Util.DoesS3BucketExistV2Async(_s3Client, bucketName); + + if (!exist) + { + _logger.LogInformation("Waiting for bucket {bucket} to be ready", bucketName); + + while (!exist) + { + await Task.Delay(2000); + exist = await Amazon.S3.Util.AmazonS3Util.DoesS3BucketExistV2Async(_s3Client, bucketName); + } + } + + return bucketName; + } + + /// + /// Create a presigned POST URL for uploading a file to an S3 bucket. + /// + /// The name of the bucket. + /// The object key (path) where the uploaded file will be stored. + /// When the presigned URL expires. + /// A CreatePresignedPostResponse object with URL and form fields. + public async Task CreatePresignedPostAsync( + string bucketName, string objectKey, DateTime expires) + { + var request = new CreatePresignedPostRequest + { + BucketName = bucketName, + Key = objectKey, + Expires = expires + }; + + return await _s3Client.CreatePresignedPostAsync(request); + } + + /// + /// Create a presigned POST URL with custom fields. + /// + /// The name of the bucket. + /// The object key (path) where the uploaded file will be stored. + /// When the presigned URL expires. + /// Dictionary of fields to add to the form. + /// A CreatePresignedPostResponse object with URL and form fields. + public async Task CreatePresignedPostWithFieldsAsync( + string bucketName, string objectKey, DateTime expires, Dictionary fields) + { + var request = new CreatePresignedPostRequest + { + BucketName = bucketName, + Key = objectKey, + Expires = expires + }; + + // Add custom fields + foreach (var field in fields) + { + request.Fields.Add(field.Key, field.Value); + } + + return await _s3Client.CreatePresignedPostAsync(request); + } + + /// + /// Create a presigned POST URL with conditions. + /// + /// The name of the bucket. + /// The object key (path) where the uploaded file will be stored. + /// When the presigned URL expires. + /// Dictionary of fields to add to the form. + /// List of conditions to apply. + /// A CreatePresignedPostResponse object with URL and form fields. + public async Task CreatePresignedPostWithConditionsAsync( + string bucketName, string objectKey, DateTime expires, + Dictionary? fields = null, List? conditions = null) + { + var request = new CreatePresignedPostRequest + { + BucketName = bucketName, + Key = objectKey, + Expires = expires + }; + + // Add custom fields if provided + if (fields != null) + { + foreach (var field in fields) + { + request.Fields.Add(field.Key, field.Value); + } + } + + // Add conditions if provided + if (conditions != null) + { + foreach (var condition in conditions) + { + request.Conditions.Add(condition); + } + } + + return await _s3Client.CreatePresignedPostAsync(request); + } + + /// + /// Upload a file to an S3 bucket. + /// + /// The name of the bucket. + /// The object key where the file will be stored. + /// The path to the file to upload. + /// The response from the PutObjectAsync call. + public async Task UploadFileAsync( + string bucketName, string objectKey, string filePath) + { + var request = new PutObjectRequest + { + BucketName = bucketName, + Key = objectKey, + FilePath = filePath + }; + + return await _s3Client.PutObjectAsync(request); + } + + /// + /// Delete an object from an S3 bucket. + /// + /// The name of the bucket. + /// The object key to delete. + /// The response from the DeleteObjectAsync call. + public async Task DeleteObjectAsync( + string bucketName, string objectKey) + { + var request = new DeleteObjectRequest + { + BucketName = bucketName, + Key = objectKey + }; + + return await _s3Client.DeleteObjectAsync(request); + } + + /// + /// Delete an S3 bucket and all its objects. + /// + /// The name of the bucket to delete. + /// A boolean value indicating the success of the operation. + public async Task DeleteBucketAsync(string bucketName) + { + try + { + // Delete all objects in the bucket + await AmazonS3Util.DeleteS3BucketWithObjectsAsync(_s3Client, bucketName); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting bucket {bucket}", bucketName); + return false; + } + } + + /// + /// Get object metadata. + /// + /// The name of the bucket. + /// The object key. + /// Object metadata. + public async Task GetObjectMetadataAsync( + string bucketName, string objectKey) + { + var request = new GetObjectMetadataRequest + { + BucketName = bucketName, + Key = objectKey + }; + + return await _s3Client.GetObjectMetadataAsync(request); + } + + /// + /// Get an object from an S3 bucket. + /// + /// The name of the bucket. + /// The object key. + /// The response from the GetObjectAsync call. + public async Task GetObjectAsync( + string bucketName, string objectKey) + { + var request = new GetObjectRequest + { + BucketName = bucketName, + Key = objectKey + }; + + return await _s3Client.GetObjectAsync(request); + } + + /// + /// Read the content of an S3 object as a string. + /// + /// The name of the bucket. + /// The object key. + /// The object content as a string. + public async Task ReadObjectContentAsync(string bucketName, string objectKey) + { + var response = await GetObjectAsync(bucketName, objectKey); + using var reader = new StreamReader(response.ResponseStream); + return await reader.ReadToEndAsync(); + } +} +// snippet-end:[S3.dotnetv4.S3Wrapper] diff --git a/dotnetv4/S3/Actions/Usings.cs b/dotnetv4/S3/Actions/Usings.cs new file mode 100644 index 00000000000..7ac09f08058 --- /dev/null +++ b/dotnetv4/S3/Actions/Usings.cs @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Net; +global using System.Net.Http; +global using System.Net.Http.Headers; +global using System.Text; +global using System.Threading.Tasks; +global using Amazon.S3; +global using Amazon.S3.Model; +global using Amazon.S3.Util; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.DependencyInjection; diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md new file mode 100644 index 00000000000..34107161810 --- /dev/null +++ b/dotnetv4/S3/README.md @@ -0,0 +1,179 @@ +# Amazon S3 code examples for the AWS SDK for .NET (v4) + +## Overview + +The examples in this section demonstrate how to use the AWS SDK for .NET (v4) with Amazon Simple Storage Service (Amazon S3). + +Amazon S3 is an object storage service that offers industry-leading scalability, data availability, security, and performance. You can use Amazon S3 to store and retrieve any amount of data at any time, from anywhere on the web. + +## ⚠️ Important + +- Running these code examples can 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 + +### Single-service actions + +Code examples that show you how to call individual Amazon S3 service functions. + +- [CreatePresignedPost](Actions/CreatePresignedPost.cs) - Shows how to create a basic presigned POST URL. +- [CreatePresignedPostWithMetadata](Actions/CreatePresignedPostWithMetadata.cs) - Shows how to add custom metadata to presigned POST URLs. +- [CreatePresignedPostWithConditions](Actions/CreatePresignedPostWithConditions.cs) - Shows how to add upload restrictions such as content type and file size. +- [CreatePresignedPostWithFilename](Actions/CreatePresignedPostWithFilename.cs) - Shows how to preserve the original filename when using presigned POST URLs. + +### Scenarios + +Code examples that show you how to accomplish specific tasks by calling multiple Amazon S3 functions. + +- [S3 CreatePresignedPost Scenario](Scenarios/S3_CreatePresignedPost/) - Shows how to create and use presigned POST URLs with Amazon S3. The scenario demonstrates: + 1. Creating an S3 bucket + 2. Creating a presigned POST URL + 3. Uploading a file using the presigned POST URL + 4. Cleaning up resources after use + +## Running the examples + +### Prerequisites + +- An AWS account. To create an account, see [AWS Free Tier](https://aws.amazon.com/free/). +- AWS credentials. For details, see the [AWS Tools and SDKs Shared Configuration and Credentials Reference Guide](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html). +- .NET 8.0 or later. For installation instructions, see the [.NET website](https://dotnet.microsoft.com/download). + +### Instructions + +The example code in this folder uses the AWS SDK for .NET (v4) to interact with Amazon S3. + +1. Clone the [AWS SDK Code Examples](https://github.com/awsdocs/aws-doc-sdk-examples) repository. +2. Open a terminal or command prompt and navigate to the `dotnetv4/S3` directory. + +#### To build and run the CreatePresignedPost scenario: + +1. Navigate to the scenario folder: + ``` + cd Scenarios/S3_CreatePresignedPost + ``` + +2. Build the scenario: + ``` + dotnet build + ``` + +3. Run the scenario in interactive mode (default): + ``` + dotnet run + ``` + + Or run in non-interactive mode: + ``` + dotnet run -- --non-interactive + ``` + +The scenario will: +- Create a temporary S3 bucket +- Create a presigned POST URL +- Upload a test file using the presigned POST URL +- Verify that the file was successfully uploaded to S3 +- Clean up the resources it created + +In interactive mode, the scenario will pause after each step and wait for you to press Enter to continue. + +#### To build and run the standalone examples: + +1. Navigate to the Actions folder: + ``` + cd Actions + ``` + +**To run CreatePresignedPost example (the default):** + +```bash +dotnet build +dotnet run +``` + +The project is configured to use S3Actions.CreatePresignedPost as the default startup class when no other is specified. + +The standalone example will: +- Create a basic presigned POST URL using the fixed bucket name "amzn-s3-demo-bucket" +- Display the URL and form fields needed for a browser upload + +#### To run other examples: + +You need to specify the StartupObject during the build phase, not during the run phase. The project will use whatever StartupObject you specify, or fall back to CreatePresignedPost if none is provided: + +**To run CreatePresignedPostWithMetadata example:** + +```bash +# If switching between examples, clean first to clear previous settings +dotnet clean + +# Build with the specific StartupObject +dotnet build /p:StartupObject=S3Actions.CreatePresignedPostWithMetadata + +# Run the example +dotnet run +``` + +**To run CreatePresignedPostWithConditions example:** + +```bash +# If switching between examples, clean first to clear previous settings +dotnet clean + +# Build with the specific StartupObject +dotnet build /p:StartupObject=S3Actions.CreatePresignedPostWithConditions + +# Run the example +dotnet run +``` + +**To run CreatePresignedPostWithFilename example:** + +```bash +# If switching between examples, clean first to clear previous settings +dotnet clean + +# Build with the specific StartupObject +dotnet build /p:StartupObject=S3Actions.CreatePresignedPostWithFilename + +# Run the example +dotnet run +``` + +> Note: The cleaning step is important when switching between different examples to ensure the new StartupObject setting takes effect. If you're building a specific example for the first time in a new terminal session, you might be able to skip the clean step. + +#### To build and run the simple HelloS3 example: + +1. Navigate to the Actions folder: + ``` + cd Actions + ``` + +```bash +# If switching between examples, clean first to clear previous settings +dotnet clean + +# Build with the specific StartupObject (if there is a HelloS3 example) +dotnet build /p:StartupObject=S3Actions.HelloS3 + +# Run the example +dotnet run +``` + +The HelloS3 example will: +- List your S3 buckets +- Display basic information about using presigned POST URLs + +## Additional resources + +- [Amazon S3 Developer Guide](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html) +- [Amazon S3 API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) +- [AWS SDK for .NET (v4) Amazon S3 Reference](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/NS3.html) +- [AWS SDK for .NET (v4) Developer Guide](https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/welcome.html) + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 diff --git a/dotnetv4/S3/S3Examples.sln b/dotnetv4/S3/S3Examples.sln new file mode 100644 index 00000000000..fedd08c6f7b --- /dev/null +++ b/dotnetv4/S3/S3Examples.sln @@ -0,0 +1,40 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33414.496 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actions", "Actions", "{863BD09D-4F48-4BFF-B5E7-941503A41F0A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Actions", "Actions\S3Actions.csproj", "{F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{C13DDD1A-438D-4E52-90FB-A496A54516C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreatePresignedPostScenario", "Scenarios\S3_CreatePresignedPost\CreatePresignedPostScenario.csproj", "{9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C51625C8-3B42-4810-BF1B-0E3C6C716FA6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Release|Any CPU.Build.0 = Release|Any CPU + {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85} = {863BD09D-4F48-4BFF-B5E7-941503A41F0A} + {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B} = {C13DDD1A-438D-4E52-90FB-A496A54516C7} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5ACB5B08-E8F8-453C-B63B-6C0C9DE67780} + EndGlobalSection +EndGlobal diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs new file mode 100644 index 00000000000..ace28bf669c --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs @@ -0,0 +1,253 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Scenarios; + +/// +/// Scenario demonstrating the complete workflow for presigned POST URLs: +/// 1. Create an S3 bucket +/// 2. Create a presigned POST URL +/// 3. Upload a file using the presigned POST URL +/// 4. Clean up resources +/// +public class CreatePresignedPostScenario +{ + private readonly S3Wrapper _s3Wrapper; + private readonly ILogger _logger; + private readonly UiMethods _uiMethods; + private readonly bool _isInteractive; + private string? _bucketName; + private string? _objectKey; + + /// + /// Constructor for the scenario class. + /// + /// The S3Wrapper instance to use. + /// The logger to use. + /// The UI methods to use. + /// Whether to run in interactive mode. + public CreatePresignedPostScenario(S3Wrapper s3Wrapper, ILogger logger, UiMethods uiMethods, bool isInteractive) + { + _s3Wrapper = s3Wrapper; + _logger = logger; + _uiMethods = uiMethods; + _isInteractive = isInteractive; + } + + /// + /// Run the scenario steps. + /// + /// A Task representing the asynchronous operation. + public async Task RunAsync() + { + try + { + // Display overview + _uiMethods.DisplayOverview(); + _uiMethods.PressEnter(_isInteractive); + + // Step 1: Create bucket + await CreateBucketAsync(); + _uiMethods.PressEnter(_isInteractive); + + // Step 2: Create presigned URL + _uiMethods.DisplayTitle("Step 2: Create presigned POST URL"); + var response = await CreatePresignedPostAsync(); + _uiMethods.PressEnter(_isInteractive); + + // Step 3: Display URL and fields + _uiMethods.DisplayTitle("Step 3: Presigned POST URL details"); + PresignedPostUtils.DisplayPresignedPostFields(response); + _uiMethods.PressEnter(_isInteractive); + + // Step 4: Upload file + _uiMethods.DisplayTitle("Step 4: Upload test file using presigned POST URL"); + await UploadFileAsync(response); + _uiMethods.PressEnter(_isInteractive); + + // Step 5: Verify file exists + await VerifyFileExistsAsync(); + _uiMethods.PressEnter(_isInteractive); + + // Step 6: Cleanup + _uiMethods.DisplayTitle("Step 6: Clean up resources"); + await CleanupAsync(); + + _uiMethods.DisplayTitle("S3 Presigned POST Scenario completed successfully!"); + _uiMethods.PressEnter(_isInteractive); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in scenario"); + Console.WriteLine($"Error: {ex.Message}"); + + // Attempt cleanup if there was an error + if (!string.IsNullOrEmpty(_bucketName)) + { + _uiMethods.DisplayTitle("Cleaning up resources after error"); + await _s3Wrapper.DeleteBucketAsync(_bucketName); + Console.WriteLine($"Cleaned up bucket: {_bucketName}"); + } + } + } + + /// + /// Create an S3 bucket for the scenario. + /// + private async Task CreateBucketAsync() + { + _uiMethods.DisplayTitle("Step 1: Create an S3 bucket"); + + // Create a unique bucket name for the scenario + _bucketName = $"presigned-post-demo-{DateTime.Now:yyyyMMddHHmmss}".ToLower(); + + Console.WriteLine($"Creating bucket: {_bucketName}"); + + await _s3Wrapper.CreateBucketAsync(_bucketName); + + Console.WriteLine($"Successfully created bucket: {_bucketName}"); + } + + /// + /// Create a presigned POST URL. + /// + private async Task CreatePresignedPostAsync() + { + _objectKey = "example-upload.txt"; + var expiration = DateTime.UtcNow.AddMinutes(10); // Short expiration for the demo + + Console.WriteLine($"Creating presigned POST URL for {_bucketName}/{_objectKey}"); + Console.WriteLine($"Expiration: {expiration} UTC"); + + var response = await _s3Wrapper.CreatePresignedPostAsync(_bucketName!, _objectKey, expiration); + + Console.WriteLine("Successfully created presigned POST URL"); + return response; + } + + /// + /// Upload a file using the presigned POST URL. + /// + private async Task UploadFileAsync(CreatePresignedPostResponse response) + { + + // Create a temporary test file to upload + string testFilePath = Path.GetTempFileName(); + string testContent = "This is a test file for the S3 presigned POST scenario."; + + await File.WriteAllTextAsync(testFilePath, testContent); + Console.WriteLine($"Created test file at: {testFilePath}"); + + // Upload the file using the presigned POST URL + Console.WriteLine("\nUploading file using the presigned POST URL..."); + var uploadResult = await UploadFileWithPresignedPostAsync(response, testFilePath); + + // Display the upload result + if (uploadResult.Success) + { + Console.WriteLine($"Upload successful! Status code: {uploadResult.StatusCode}"); + Console.WriteLine($"Response: {uploadResult.Response}"); + } + else + { + Console.WriteLine($"Upload failed with status code: {uploadResult.StatusCode}"); + Console.WriteLine($"Error: {uploadResult.Response}"); + throw new Exception("File upload failed"); + } + + // Clean up the temporary file + File.Delete(testFilePath); + Console.WriteLine("Temporary file deleted"); + } + + /// + /// Helper method to upload a file using a presigned POST URL. + /// + private async Task<(bool Success, HttpStatusCode StatusCode, string Response)> UploadFileWithPresignedPostAsync( + CreatePresignedPostResponse response, + string filePath) + { + try + { + _logger.LogInformation("Uploading file {filePath} using presigned POST URL", filePath); + + // Create HttpClient to send the request + using var httpClient = new HttpClient(); + using var formContent = new MultipartFormDataContent(); + + // Add all the fields from the presigned POST response + foreach (var field in response.Fields) + { + formContent.Add(new StringContent(field.Value), field.Key); + } + + // Add the file content + var fileStream = File.OpenRead(filePath); + var fileName = Path.GetFileName(filePath); + var fileContent = new StreamContent(fileStream); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); + formContent.Add(fileContent, "file", fileName); + + // Send the POST request + var httpResponse = await httpClient.PostAsync(response.Url, formContent); + var responseContent = await httpResponse.Content.ReadAsStringAsync(); + + // Log and return the result + _logger.LogInformation("Upload completed with status code {statusCode}", httpResponse.StatusCode); + + return (httpResponse.IsSuccessStatusCode, httpResponse.StatusCode, responseContent); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error uploading file"); + return (false, HttpStatusCode.InternalServerError, ex.Message); + } + } + + /// + /// Verify that the uploaded file exists in the S3 bucket. + /// + private async Task VerifyFileExistsAsync() + { + _uiMethods.DisplayTitle("Step 5: Verify uploaded file exists"); + + Console.WriteLine($"Checking if file exists at {_bucketName}/{_objectKey}..."); + + try + { + var metadata = await _s3Wrapper.GetObjectMetadataAsync(_bucketName!, _objectKey!); + + Console.WriteLine($"File verification successful! File exists in the bucket."); + Console.WriteLine($"File size: {metadata.ContentLength} bytes"); + Console.WriteLine($"File type: {metadata.Headers.ContentType}"); + Console.WriteLine($"Last modified: {metadata.LastModified}"); + } + catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) + { + Console.WriteLine($"Error: File was not found in the bucket."); + throw; + } + } + + /// + /// Clean up resources created by the scenario. + /// + private async Task CleanupAsync() + { + + if (!string.IsNullOrEmpty(_bucketName)) + { + Console.WriteLine($"Deleting bucket {_bucketName} and its contents..."); + bool result = await _s3Wrapper.DeleteBucketAsync(_bucketName); + + if (result) + { + Console.WriteLine("Bucket deleted successfully"); + } + else + { + Console.WriteLine("Failed to delete bucket - it may have been already deleted"); + } + } + } +} diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.csproj b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.csproj new file mode 100644 index 00000000000..24963eefd86 --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs new file mode 100644 index 00000000000..842ec2c3084 --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Scenarios; + +/// +/// Main entry point for the scenario program. +/// +public class Program +{ + /// + /// Main method that sets up the services and runs the scenario. + /// + /// Command line arguments. Use --non-interactive to run in non-interactive mode. + /// Async task. + public static async Task Main(string[] args) + { + // Check for non-interactive mode + bool isInteractive = !args.Contains("--non-interactive"); + + // Set up dependency injection for Amazon S3 + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + // Get the services + var s3Client = host.Services.GetRequiredService(); + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + + // Create the wrapper instance + var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); + + // Create UI methods + var uiMethods = new UiMethods(); + + // Create the scenario instance + var logger = loggerFactory.CreateLogger(); + var scenario = new CreatePresignedPostScenario(s3Wrapper, logger, uiMethods, isInteractive); + + // Run the scenario + await scenario.RunAsync(); + } +} diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs new file mode 100644 index 00000000000..ddee406254a --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs @@ -0,0 +1,50 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Scenarios; + +/// +/// UI helper methods for the S3 presigned POST scenario. +/// +public class UiMethods +{ + public readonly string SepBar = new string('-', 88); + + /// + /// Show information about the scenario. + /// + public void DisplayOverview() + { + DisplayTitle("Welcome to the Amazon S3 Presigned POST URL Scenario"); + + Console.WriteLine("This example application does the following:"); + Console.WriteLine("\t 1. Creates an S3 bucket with a unique name"); + Console.WriteLine("\t 2. Creates a presigned POST URL for the bucket"); + Console.WriteLine("\t 3. Displays the URL and form fields needed for browser uploads"); + Console.WriteLine("\t 4. Uploads a test file using the presigned POST URL"); + Console.WriteLine("\t 5. Verifies the file was successfully uploaded to S3"); + Console.WriteLine("\t 6. Cleans up the resources (bucket and test file)"); + } + + /// + /// Display a message and wait until the user presses enter if in interactive mode. + /// + public void PressEnter(bool interactive) + { + Console.Write("\nPlease press to continue. "); + if (interactive) + _ = Console.ReadLine(); + } + + /// + /// Display a line of hyphens, the text of the title and another + /// line of hyphens. + /// + /// The string to be displayed. + public void DisplayTitle(string strTitle) + { + Console.WriteLine(SepBar); + Console.WriteLine(strTitle); + Console.WriteLine(SepBar); + } +} diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs new file mode 100644 index 00000000000..45c843f8080 --- /dev/null +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs @@ -0,0 +1,17 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Net; +global using System.Net.Http; +global using System.Net.Http.Headers; +global using System.Text; +global using System.Threading.Tasks; +global using Amazon.S3; +global using Amazon.S3.Model; +global using Amazon.S3.Util; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.DependencyInjection; +global using S3Actions; diff --git a/dotnetv4/S3/Tests/S3Tests.csproj b/dotnetv4/S3/Tests/S3Tests.csproj new file mode 100644 index 00000000000..b4cbb82748b --- /dev/null +++ b/dotnetv4/S3/Tests/S3Tests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + + false + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/dotnetv4/S3/Tests/S3WrapperTests.cs b/dotnetv4/S3/Tests/S3WrapperTests.cs new file mode 100644 index 00000000000..af486a79b80 --- /dev/null +++ b/dotnetv4/S3/Tests/S3WrapperTests.cs @@ -0,0 +1,77 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +namespace S3Tests; + +/// +/// Integration tests for the Amazon Simple Storage Service (Amazon S3) +/// presigned POST URL scenario. +/// +public class S3WrapperTests +{ + private AmazonS3Client _client = null!; + private S3Wrapper _s3Wrapper = null!; + + /// + /// Verifies the scenario with an integration test. No errors should be logged. + /// + /// Async task. + [Fact] + [Trait("Category", "Integration")] + public async Task TestScenario() + { + // Arrange. + var loggerScenarioMock = new Mock>(); + var loggerWrapperMock = new Mock>(); + var uiMethods = new S3Scenarios.UiMethods(); + bool isInteractive = false; + + _client = new AmazonS3Client(); + _s3Wrapper = new S3Wrapper(_client, loggerWrapperMock.Object); + + var scenario = new S3Scenarios.CreatePresignedPostScenario( + _s3Wrapper, + loggerScenarioMock.Object, + uiMethods, + isInteractive); + + // Set up verification for error logging. + loggerScenarioMock.Setup(logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>() + )); + + loggerWrapperMock.Setup(logger => logger.Log( + It.Is(logLevel => logLevel == LogLevel.Error), + It.IsAny(), + It.Is((@object, @type) => true), + It.IsAny(), + It.IsAny>() + )); + + // Act. + await scenario.RunAsync(); + + // Assert no exceptions or 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); + + loggerWrapperMock.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/S3/Tests/Usings.cs b/dotnetv4/S3/Tests/Usings.cs new file mode 100644 index 00000000000..8197e56ff5b --- /dev/null +++ b/dotnetv4/S3/Tests/Usings.cs @@ -0,0 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +global using Amazon.S3; +global using Microsoft.Extensions.Logging; +global using Moq; +global using S3Scenarios; +global using S3Actions; +global using Xunit; From 4cc753307b4cb542f51ae8f02b147b5b22b73877 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 12:04:49 -0400 Subject: [PATCH 02/21] update console --- dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs index 41ffca178c9..3633fae5c79 100644 --- a/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs +++ b/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs @@ -93,7 +93,7 @@ public static async Task Main(string[] args) PresignedPostUtils.DisplayPresignedPostFields(response); Console.WriteLine("\nThis example adds these restrictions:"); - Console.WriteLine(" • Content-Type must start with 'text/' (enforced by policy)"); + Console.WriteLine(" • Content-Type must start with 'text/plain' (enforced by policy)"); Console.WriteLine(" • File size must be between 1 byte and 1 MB (enforced by policy)"); Console.WriteLine("\nIf these conditions are not met, the upload will be rejected."); From a7b2ae6087bb72d3927687d99b968fc992009268 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 12:12:30 -0400 Subject: [PATCH 03/21] add snippets --- dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs | 2 ++ dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs | 2 ++ dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs | 2 ++ .../S3_CreatePresignedPost/CreatePresignedPostScenario.cs | 2 ++ dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs | 2 ++ 5 files changed, 10 insertions(+) diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs index 3633fae5c79..c11ff55613e 100644 --- a/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs +++ b/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs @@ -3,6 +3,7 @@ namespace S3Actions; +// snippet-start:[S3.dotnetv4.CreatePresignedPostWithConditions] /// /// Demonstrates how to create Amazon S3 presigned POST URLs with conditions. /// This example shows how to add restrictions to uploads such as content type and size limits. @@ -109,3 +110,4 @@ public static async Task Main(string[] args) } } } +// snippet-end:[S3.dotnetv4.CreatePresignedPostWithConditions] diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs index b4c1662fdfa..a90cf794408 100644 --- a/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs +++ b/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs @@ -3,6 +3,7 @@ namespace S3Actions; +// snippet-start:[S3.dotnetv4.CreatePresignedPostWithFilename] /// /// Demonstrates how to create Amazon S3 presigned POST URLs with the ${filename} variable. /// This example shows how to preserve the original filename when uploading files to S3. @@ -100,3 +101,4 @@ public static async Task Main(string[] args) } } } +// snippet-end:[S3.dotnetv4.CreatePresignedPostWithFilename] diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs index fc3b9c499e1..8d7c12cc566 100644 --- a/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs +++ b/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs @@ -3,6 +3,7 @@ namespace S3Actions; +// snippet-start:[S3.dotnetv4.CreatePresignedPostWithMetadata] /// /// Demonstrates how to create an Amazon S3 presigned POST URL with metadata. /// This example shows how to add custom metadata to objects uploaded via presigned POST URLs. @@ -107,3 +108,4 @@ public static async Task Main(string[] args) } } } +// snippet-end:[S3.dotnetv4.CreatePresignedPostWithMetadata] diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs index ace28bf669c..b664136fd5f 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs @@ -3,6 +3,7 @@ namespace S3Scenarios; +// snippet-start:[S3.dotnetv4.CreatePresignedPostScenario] /// /// Scenario demonstrating the complete workflow for presigned POST URLs: /// 1. Create an S3 bucket @@ -251,3 +252,4 @@ private async Task CleanupAsync() } } } +// snippet-end:[S3.dotnetv4.CreatePresignedPostScenario] diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs index 842ec2c3084..6c6a9ac8a64 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs @@ -3,6 +3,7 @@ namespace S3Scenarios; +// snippet-start:[S3.dotnetv4.CreatePresignedPostProgram] /// /// Main entry point for the scenario program. /// @@ -44,3 +45,4 @@ public static async Task Main(string[] args) await scenario.RunAsync(); } } +// snippet-end:[S3.dotnetv4.CreatePresignedPostProgram] From eebb39973ef5fd32dafdff0580a3955ee74c85eb Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 12:15:27 -0400 Subject: [PATCH 04/21] remove extra --- dotnetv4/S3/README.md | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md index 34107161810..e10f752ea88 100644 --- a/dotnetv4/S3/README.md +++ b/dotnetv4/S3/README.md @@ -145,28 +145,6 @@ dotnet run > Note: The cleaning step is important when switching between different examples to ensure the new StartupObject setting takes effect. If you're building a specific example for the first time in a new terminal session, you might be able to skip the clean step. -#### To build and run the simple HelloS3 example: - -1. Navigate to the Actions folder: - ``` - cd Actions - ``` - -```bash -# If switching between examples, clean first to clear previous settings -dotnet clean - -# Build with the specific StartupObject (if there is a HelloS3 example) -dotnet build /p:StartupObject=S3Actions.HelloS3 - -# Run the example -dotnet run -``` - -The HelloS3 example will: -- List your S3 buckets -- Display basic information about using presigned POST URLs - ## Additional resources - [Amazon S3 Developer Guide](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html) From 2a0781ec1c1b0a44fbce6ba3e975a6fed32d9ed3 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 13:05:03 -0400 Subject: [PATCH 05/21] update examples --- .doc_gen/metadata/s3_metadata.yaml | 28 ++++ dotnetv4/S3/Actions/CreatePresignedPost.cs | 86 ++++++---- .../CreatePresignedPostWithConditions.cs | 113 ------------- .../CreatePresignedPostWithFilename.cs | 104 ------------ .../CreatePresignedPostWithMetadata.cs | 111 ------------- dotnetv4/S3/Actions/S3Actions.csproj | 6 +- dotnetv4/S3/Actions/S3Wrapper.cs | 2 - dotnetv4/S3/README.md | 157 ------------------ .../S3_CreatePresignedPost/Program.cs | 2 - 9 files changed, 87 insertions(+), 522 deletions(-) delete mode 100644 dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs delete mode 100644 dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs delete mode 100644 dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs delete mode 100644 dotnetv4/S3/README.md diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index 462f07881e0..d39fce60d32 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -3705,3 +3705,31 @@ s3_Scenario_DoesBucketExist: - s3.java2.does-bucket-exist-main services: s3: {GetBucketAcl} + +s3_CreatePresignedPost: + languages: + .NET: + versions: + - sdk_version: 4 + github: dotnetv4/S3 + excerpts: + - description: + snippet_tags: + - S3.dotnetv4.CreatePresignedPost + services: + s3: {CreatePresignedPost} + +s3_Scenario_CreatePresignedPost: + title: Create and use presigned POST URLs + category: Scenarios + languages: + .NET: + versions: + - sdk_version: 4 + github: dotnetv4/S3/Scenarios/S3_CreatePresignedPost + excerpts: + - description: + snippet_tags: + - S3.dotnetv4.CreatePresignedPostScenario + services: + s3: {CreateBucket, CreatePresignedPost, GetObjectMetadata, DeleteBucket} diff --git a/dotnetv4/S3/Actions/CreatePresignedPost.cs b/dotnetv4/S3/Actions/CreatePresignedPost.cs index 8e78565f2b9..ae37cac7fc7 100644 --- a/dotnetv4/S3/Actions/CreatePresignedPost.cs +++ b/dotnetv4/S3/Actions/CreatePresignedPost.cs @@ -5,41 +5,63 @@ namespace S3Actions; // snippet-start:[S3.dotnetv4.CreatePresignedPost] /// -/// Demonstrates how to use Amazon Simple Storage Service (Amazon S3) -/// CreatePresignedPost functionality to generate a pre-signed URL for browser-based uploads. +/// Demonstrates how to create Amazon S3 presigned POST URLs with both conditions and filename variables. +/// This example shows how to add restrictions to uploads and preserve original filenames. /// public class CreatePresignedPost { /// - /// Create a basic presigned POST URL. + /// Create a presigned POST URL with both filename variable and conditions. /// /// The S3Wrapper instance to use. /// The logger to use. - /// The name of the bucket where the file will be uploaded. - /// The object key (path) where the file will be stored. + /// The name of the bucket. + /// The prefix for the key, final key will be prefix + actual filename. /// A CreatePresignedPostResponse containing the URL and form fields. - public static async Task CreateBasicPresignedPost( - S3Wrapper s3Wrapper, - ILogger logger, - string bucketName, - string objectKey) + public static async Task CreateWithFilenameAndConditions( + S3Wrapper s3Wrapper, + ILogger logger, + string bucketName, + string keyPrefix) { - // Set expiration time (maximum is 7 days from now) - var expiration = DateTime.UtcNow.AddHours(1); // 1 hour expiration - - logger.LogInformation("Creating presigned POST URL for {bucket}/{key} with expiration {expiration}", - bucketName, objectKey, expiration); - - var response = await s3Wrapper.CreatePresignedPostAsync(bucketName, objectKey, expiration); + var expiration = DateTime.UtcNow.AddHours(1); + + // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename + string objectKey = keyPrefix + "${filename}"; + + // Add custom metadata and fields + var fields = new Dictionary + { + // Add a custom metadata field + { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, + + // Return HTTP 201 on successful upload + { "success_action_status", "201" }, + + // Set the content type + { "Content-Type", "text/plain" } + }; + + // Add policy conditions + var conditions = new List + { + // File size must be between 1 byte and 1 MB + S3PostCondition.ContentLengthRange(1, 1048576) + }; - logger.LogInformation("Successfully created presigned POST URL: {url}", response.Url); + logger.LogInformation("Creating presigned POST URL with filename variable and conditions for {bucket}/{key}", + bucketName, objectKey); + + var response = await s3Wrapper.CreatePresignedPostWithConditionsAsync( + bucketName, objectKey, expiration, fields, conditions); + logger.LogInformation("Successfully created presigned POST URL with filename variable and conditions"); + return response; } - /// - /// Main method that demonstrates the CreatePresignedPost functionality. + /// Main method that demonstrates creating and using presigned POST URLs with combined features. /// /// Command line arguments. Not used in this example. /// Async task. @@ -61,8 +83,8 @@ public static async Task Main(string[] args) // Create the wrapper instance var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); - Console.WriteLine("Amazon S3 CreatePresignedPost Basic Example"); - Console.WriteLine("=========================================="); + Console.WriteLine("Amazon S3 CreatePresignedPost Example"); + Console.WriteLine("==================================="); try { @@ -70,15 +92,23 @@ public static async Task Main(string[] args) Console.WriteLine($"Using bucket: {bucketName}"); Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); - // Create a simple example object key - string objectKey = "example-upload.txt"; + // Create a key prefix for this example + string keyPrefix = "uploads/"; - // Generate the presigned POST URL - Console.WriteLine("\nCreating a presigned POST URL..."); - var response = await CreateBasicPresignedPost(s3Wrapper, logger, bucketName, objectKey); + // Generate the presigned POST URL with combined features + Console.WriteLine("\nCreating a presigned POST URL with both filename preservation and upload restrictions..."); + var response = await CreateWithFilenameAndConditions(s3Wrapper, logger, bucketName, keyPrefix); - // Display the URL and fields that would be needed in an HTML form + // Display the URL and fields + Console.WriteLine("\nPresigned POST URL with combined features created successfully:"); PresignedPostUtils.DisplayPresignedPostFields(response); + + Console.WriteLine("\nThis example combines multiple features:"); + Console.WriteLine(" • Uses ${filename} to preserve the original filename in the 'uploads/' prefix"); + Console.WriteLine(" • Adds custom metadata (x-amz-meta-uploaded-by)"); + Console.WriteLine(" • Sets success_action_status to return HTTP 201 on success"); + Console.WriteLine(" • Restricts content type to text/plain"); + Console.WriteLine(" • Limits file size to between 1 byte and 1 MB"); Console.WriteLine("\nExample completed successfully."); } diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs deleted file mode 100644 index c11ff55613e..00000000000 --- a/dotnetv4/S3/Actions/CreatePresignedPostWithConditions.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace S3Actions; - -// snippet-start:[S3.dotnetv4.CreatePresignedPostWithConditions] -/// -/// Demonstrates how to create Amazon S3 presigned POST URLs with conditions. -/// This example shows how to add restrictions to uploads such as content type and size limits. -/// -public class CreatePresignedPostWithConditions -{ - /// - /// Create a presigned POST URL with conditions to restrict uploads. - /// - /// The S3Wrapper instance to use. - /// The logger to use. - /// The name of the bucket. - /// The object key where the uploaded file will be stored. - /// A CreatePresignedPostResponse containing the URL and form fields. - public static async Task CreateWithConditions( - S3Wrapper s3Wrapper, - ILogger logger, - string bucketName, - string objectKey) - { - var expiration = DateTime.UtcNow.AddHours(1); - - var fields = new Dictionary - { - { "Content-Type", "text/plain" } - }; - - var conditions = new List - { - // File size must be between 1 byte and 1 MB - S3PostCondition.ContentLengthRange(1, 1048576) - }; - - logger.LogInformation("Creating presigned POST URL with conditions for {bucket}/{key}", - bucketName, objectKey); - - var response = await s3Wrapper.CreatePresignedPostWithConditionsAsync( - bucketName, objectKey, expiration, fields, conditions); - - logger.LogInformation("Successfully created presigned POST URL with {fieldCount} fields and {conditionCount} conditions", - fields.Count, conditions.Count); - - return response; - } - - - /// - /// Main method that demonstrates creating and using presigned POST URLs with conditions. - /// - /// Command line arguments. Not used in this example. - /// Async task. - public static async Task Main(string[] args) - { - // Set up dependency injection for Amazon S3 - using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureServices((_, services) => - services.AddAWSService() - .AddTransient() - ) - .Build(); - - // Get the services - var s3Client = host.Services.GetRequiredService(); - var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - var logger = loggerFactory.CreateLogger(); - - // Create the wrapper instance - var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); - - Console.WriteLine("Amazon S3 CreatePresignedPost with Conditions Example"); - Console.WriteLine("==================================================="); - - try - { - const string bucketName = "amzn-s3-demo-bucket"; - Console.WriteLine($"Using bucket: {bucketName}"); - Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); - - // Create an object key for this example - string objectKey = "conditions-example.txt"; - - // Generate the presigned POST URL with conditions - Console.WriteLine("\nCreating a presigned POST URL with upload restrictions..."); - var response = await CreateWithConditions(s3Wrapper, logger, bucketName, objectKey); - - // Display the URL and fields - Console.WriteLine("\nPresigned POST URL with conditions created successfully:"); - PresignedPostUtils.DisplayPresignedPostFields(response); - - Console.WriteLine("\nThis example adds these restrictions:"); - Console.WriteLine(" • Content-Type must start with 'text/plain' (enforced by policy)"); - Console.WriteLine(" • File size must be between 1 byte and 1 MB (enforced by policy)"); - Console.WriteLine("\nIf these conditions are not met, the upload will be rejected."); - - Console.WriteLine("\nExample completed successfully."); - } - catch (AmazonS3Exception ex) - { - Console.WriteLine($"Amazon S3 error: {ex.Message}"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - } -} -// snippet-end:[S3.dotnetv4.CreatePresignedPostWithConditions] diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs deleted file mode 100644 index a90cf794408..00000000000 --- a/dotnetv4/S3/Actions/CreatePresignedPostWithFilename.cs +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace S3Actions; - -// snippet-start:[S3.dotnetv4.CreatePresignedPostWithFilename] -/// -/// Demonstrates how to create Amazon S3 presigned POST URLs with the ${filename} variable. -/// This example shows how to preserve the original filename when uploading files to S3. -/// -public class CreatePresignedPostWithFilename -{ - /// - /// Create a presigned POST URL with the ${filename} variable to preserve original filenames. - /// - /// The S3Wrapper instance to use. - /// The logger to use. - /// The name of the bucket. - /// The prefix for the key, final key will be prefix + actual filename. - /// A CreatePresignedPostResponse containing the URL and form fields. - public static async Task CreateWithFilename( - S3Wrapper s3Wrapper, - ILogger logger, - string bucketName, - string keyPrefix) - { - var expiration = DateTime.UtcNow.AddHours(1); - - // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename - string objectKey = keyPrefix + "${filename}"; - - logger.LogInformation("Creating presigned POST URL with filename variable for {bucket}/{key}", - bucketName, objectKey); - - var response = await s3Wrapper.CreatePresignedPostAsync(bucketName, objectKey, expiration); - - logger.LogInformation("Successfully created presigned POST URL with filename variable"); - logger.LogInformation("When a file is uploaded, it will be stored at: {prefix} + actual filename", - keyPrefix); - - return response; - } - - - /// - /// Main method that demonstrates creating and using presigned POST URLs with the ${filename} variable. - /// - /// Command line arguments. Not used in this example. - /// Async task. - public static async Task Main(string[] args) - { - // Set up dependency injection for Amazon S3 - using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureServices((_, services) => - services.AddAWSService() - .AddTransient() - ) - .Build(); - - // Get the services - var s3Client = host.Services.GetRequiredService(); - var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - var logger = loggerFactory.CreateLogger(); - - // Create the wrapper instance - var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); - - Console.WriteLine("Amazon S3 CreatePresignedPost with ${filename} Variable Example"); - Console.WriteLine("========================================================"); - - try - { - const string bucketName = "amzn-s3-demo-bucket"; - Console.WriteLine($"Using bucket: {bucketName}"); - Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); - - // Create a key prefix for this example - string keyPrefix = "uploads/"; - - // Generate the presigned POST URL with filename variable - Console.WriteLine("\nCreating a presigned POST URL that preserves the original filename..."); - var response = await CreateWithFilename(s3Wrapper, logger, bucketName, keyPrefix); - - // Display the URL and fields - Console.WriteLine("\nPresigned POST URL with filename variable created successfully:"); - PresignedPostUtils.DisplayPresignedPostFields(response); - - Console.WriteLine("\nThis example uses ${filename} in the key:"); - Console.WriteLine(" • The uploaded file will be stored with its original name in the 'uploads/' prefix"); - Console.WriteLine(" • For example, if you upload 'document.pdf', it will be stored as 'uploads/document.pdf'"); - - Console.WriteLine("\nExample completed successfully."); - } - catch (AmazonS3Exception ex) - { - Console.WriteLine($"Amazon S3 error: {ex.Message}"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - } -} -// snippet-end:[S3.dotnetv4.CreatePresignedPostWithFilename] diff --git a/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs b/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs deleted file mode 100644 index 8d7c12cc566..00000000000 --- a/dotnetv4/S3/Actions/CreatePresignedPostWithMetadata.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace S3Actions; - -// snippet-start:[S3.dotnetv4.CreatePresignedPostWithMetadata] -/// -/// Demonstrates how to create an Amazon S3 presigned POST URL with metadata. -/// This example shows how to add custom metadata to objects uploaded via presigned POST URLs. -/// -public class CreatePresignedPostWithMetadata -{ - /// - /// Create a presigned POST URL with metadata. - /// - /// The S3Wrapper instance to use. - /// The logger to use. - /// The name of the bucket. - /// The object key where the uploaded file will be stored. - /// A CreatePresignedPostResponse containing the URL and form fields. - public static async Task CreateWithMetadata( - S3Wrapper s3Wrapper, - ILogger logger, - string bucketName, - string objectKey) - { - var expiration = DateTime.UtcNow.AddHours(1); - - var fields = new Dictionary - { - // Add a custom metadata field - { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, - - // Return HTTP 201 on successful upload - { "success_action_status", "201" } - }; - - logger.LogInformation("Creating presigned POST URL with metadata for {bucket}/{key}", - bucketName, objectKey); - - var response = await s3Wrapper.CreatePresignedPostWithFieldsAsync( - bucketName, objectKey, expiration, fields); - - logger.LogInformation("Successfully created presigned POST URL with {count} custom fields", - fields.Count); - - return response; - } - - - /// - /// Main method that demonstrates creating and using presigned POST URLs with metadata. - /// - /// Command line arguments. Not used in this example. - /// Async task. - public static async Task Main(string[] args) - { - // Set up dependency injection for Amazon S3 - using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureServices((_, services) => - services.AddAWSService() - .AddTransient() - ) - .Build(); - - // Get the services - var s3Client = host.Services.GetRequiredService(); - var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - var logger = loggerFactory.CreateLogger(); - - // Create the wrapper instance - var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); - - Console.WriteLine("Amazon S3 CreatePresignedPost with Metadata Example"); - Console.WriteLine("=================================================="); - - try - { - const string bucketName = "amzn-s3-demo-bucket"; - Console.WriteLine($"Using bucket: {bucketName}"); - Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); - - // Create an object key for this example - string objectKey = "metadata-example.txt"; - - // Generate the presigned POST URL with metadata - Console.WriteLine("\nCreating a presigned POST URL with metadata fields..."); - var response = await CreateWithMetadata(s3Wrapper, logger, bucketName, objectKey); - - // Display the URL and fields - Console.WriteLine("\nPresigned POST URL with metadata created successfully:"); - PresignedPostUtils.DisplayPresignedPostFields(response); - - Console.WriteLine("\nThis example adds:"); - Console.WriteLine(" • x-amz-meta-uploaded-by: dotnet-sdk-example - adds custom metadata"); - Console.WriteLine(" • success_action_status: 201 - returns HTTP 201 on successful upload"); - - - Console.WriteLine("\nExample completed successfully."); - } - catch (AmazonS3Exception ex) - { - Console.WriteLine($"Amazon S3 error: {ex.Message}"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - } -} -// snippet-end:[S3.dotnetv4.CreatePresignedPostWithMetadata] diff --git a/dotnetv4/S3/Actions/S3Actions.csproj b/dotnetv4/S3/Actions/S3Actions.csproj index 9c41c00dc76..4143abf079b 100644 --- a/dotnetv4/S3/Actions/S3Actions.csproj +++ b/dotnetv4/S3/Actions/S3Actions.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,10 +7,6 @@ enable - - S3Actions.CreatePresignedPost - - diff --git a/dotnetv4/S3/Actions/S3Wrapper.cs b/dotnetv4/S3/Actions/S3Wrapper.cs index 1a6bc9408fb..3107b89305a 100644 --- a/dotnetv4/S3/Actions/S3Wrapper.cs +++ b/dotnetv4/S3/Actions/S3Wrapper.cs @@ -3,7 +3,6 @@ namespace S3Actions; -// snippet-start:[S3.dotnetv4.S3Wrapper] /// /// Wrapper methods for common Amazon Simple Storage Service (Amazon S3) /// operations. @@ -255,4 +254,3 @@ public async Task ReadObjectContentAsync(string bucketName, string objec return await reader.ReadToEndAsync(); } } -// snippet-end:[S3.dotnetv4.S3Wrapper] diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md deleted file mode 100644 index e10f752ea88..00000000000 --- a/dotnetv4/S3/README.md +++ /dev/null @@ -1,157 +0,0 @@ -# Amazon S3 code examples for the AWS SDK for .NET (v4) - -## Overview - -The examples in this section demonstrate how to use the AWS SDK for .NET (v4) with Amazon Simple Storage Service (Amazon S3). - -Amazon S3 is an object storage service that offers industry-leading scalability, data availability, security, and performance. You can use Amazon S3 to store and retrieve any amount of data at any time, from anywhere on the web. - -## ⚠️ Important - -- Running these code examples can 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 - -### Single-service actions - -Code examples that show you how to call individual Amazon S3 service functions. - -- [CreatePresignedPost](Actions/CreatePresignedPost.cs) - Shows how to create a basic presigned POST URL. -- [CreatePresignedPostWithMetadata](Actions/CreatePresignedPostWithMetadata.cs) - Shows how to add custom metadata to presigned POST URLs. -- [CreatePresignedPostWithConditions](Actions/CreatePresignedPostWithConditions.cs) - Shows how to add upload restrictions such as content type and file size. -- [CreatePresignedPostWithFilename](Actions/CreatePresignedPostWithFilename.cs) - Shows how to preserve the original filename when using presigned POST URLs. - -### Scenarios - -Code examples that show you how to accomplish specific tasks by calling multiple Amazon S3 functions. - -- [S3 CreatePresignedPost Scenario](Scenarios/S3_CreatePresignedPost/) - Shows how to create and use presigned POST URLs with Amazon S3. The scenario demonstrates: - 1. Creating an S3 bucket - 2. Creating a presigned POST URL - 3. Uploading a file using the presigned POST URL - 4. Cleaning up resources after use - -## Running the examples - -### Prerequisites - -- An AWS account. To create an account, see [AWS Free Tier](https://aws.amazon.com/free/). -- AWS credentials. For details, see the [AWS Tools and SDKs Shared Configuration and Credentials Reference Guide](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html). -- .NET 8.0 or later. For installation instructions, see the [.NET website](https://dotnet.microsoft.com/download). - -### Instructions - -The example code in this folder uses the AWS SDK for .NET (v4) to interact with Amazon S3. - -1. Clone the [AWS SDK Code Examples](https://github.com/awsdocs/aws-doc-sdk-examples) repository. -2. Open a terminal or command prompt and navigate to the `dotnetv4/S3` directory. - -#### To build and run the CreatePresignedPost scenario: - -1. Navigate to the scenario folder: - ``` - cd Scenarios/S3_CreatePresignedPost - ``` - -2. Build the scenario: - ``` - dotnet build - ``` - -3. Run the scenario in interactive mode (default): - ``` - dotnet run - ``` - - Or run in non-interactive mode: - ``` - dotnet run -- --non-interactive - ``` - -The scenario will: -- Create a temporary S3 bucket -- Create a presigned POST URL -- Upload a test file using the presigned POST URL -- Verify that the file was successfully uploaded to S3 -- Clean up the resources it created - -In interactive mode, the scenario will pause after each step and wait for you to press Enter to continue. - -#### To build and run the standalone examples: - -1. Navigate to the Actions folder: - ``` - cd Actions - ``` - -**To run CreatePresignedPost example (the default):** - -```bash -dotnet build -dotnet run -``` - -The project is configured to use S3Actions.CreatePresignedPost as the default startup class when no other is specified. - -The standalone example will: -- Create a basic presigned POST URL using the fixed bucket name "amzn-s3-demo-bucket" -- Display the URL and form fields needed for a browser upload - -#### To run other examples: - -You need to specify the StartupObject during the build phase, not during the run phase. The project will use whatever StartupObject you specify, or fall back to CreatePresignedPost if none is provided: - -**To run CreatePresignedPostWithMetadata example:** - -```bash -# If switching between examples, clean first to clear previous settings -dotnet clean - -# Build with the specific StartupObject -dotnet build /p:StartupObject=S3Actions.CreatePresignedPostWithMetadata - -# Run the example -dotnet run -``` - -**To run CreatePresignedPostWithConditions example:** - -```bash -# If switching between examples, clean first to clear previous settings -dotnet clean - -# Build with the specific StartupObject -dotnet build /p:StartupObject=S3Actions.CreatePresignedPostWithConditions - -# Run the example -dotnet run -``` - -**To run CreatePresignedPostWithFilename example:** - -```bash -# If switching between examples, clean first to clear previous settings -dotnet clean - -# Build with the specific StartupObject -dotnet build /p:StartupObject=S3Actions.CreatePresignedPostWithFilename - -# Run the example -dotnet run -``` - -> Note: The cleaning step is important when switching between different examples to ensure the new StartupObject setting takes effect. If you're building a specific example for the first time in a new terminal session, you might be able to skip the clean step. - -## Additional resources - -- [Amazon S3 Developer Guide](https://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html) -- [Amazon S3 API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) -- [AWS SDK for .NET (v4) Amazon S3 Reference](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/NS3.html) -- [AWS SDK for .NET (v4) Developer Guide](https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/welcome.html) - ---- - -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs index 6c6a9ac8a64..842ec2c3084 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs @@ -3,7 +3,6 @@ namespace S3Scenarios; -// snippet-start:[S3.dotnetv4.CreatePresignedPostProgram] /// /// Main entry point for the scenario program. /// @@ -45,4 +44,3 @@ public static async Task Main(string[] args) await scenario.RunAsync(); } } -// snippet-end:[S3.dotnetv4.CreatePresignedPostProgram] From b01250af1519dad93048fc1bf80890847acdbffa Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 13:20:37 -0400 Subject: [PATCH 06/21] fix readme --- dotnetv4/S3/README.md | 97 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 dotnetv4/S3/README.md diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md new file mode 100644 index 00000000000..62c560c1ff7 --- /dev/null +++ b/dotnetv4/S3/README.md @@ -0,0 +1,97 @@ +# Amazon S3 code examples for the SDK for .NET (v4) + +## Overview + +Shows how to use the AWS SDK for .NET (v4) to work with Amazon Simple Storage Service (Amazon S3). + + + + +_Amazon S3 is storage for the internet. You can use Amazon S3 to store and retrieve any amount of data at any time, from anywhere on the web._ + +## ⚠ Important + +* 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 + +### Prerequisites + +For prerequisites, see the [README](../README.md#Prerequisites) in the `dotnetv4` folder. + + + + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [CreatePresignedPost](Actions/CreatePresignedPost.cs#L6) + +### Scenarios + +Code examples that show you how to accomplish a specific task by calling multiple +functions within the same service. + +- [](Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs) + + + + + +## Run the examples + +### Instructions + + + + + + + +#### + +This example shows you how to do the following: + + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../README.md#Tests) +in the `dotnetv4` folder. + + + + + + +## Additional resources + +- [Amazon S3 User Guide](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) +- [Amazon S3 API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) +- [SDK for .NET (v4) Amazon S3 reference](https://docs.aws.amazon.com/sdkfornet/v4/apidocs/items/S3/NS3.html) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 From e1e2f4291dd6dce365281e396f8d82217f63539f Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 13:29:41 -0400 Subject: [PATCH 07/21] update metadata --- .doc_gen/metadata/s3_metadata.yaml | 5 +- dotnetv4/S3/Actions/S3Wrapper.cs | 100 +---------------------------- dotnetv4/S3/README.md | 2 +- 3 files changed, 7 insertions(+), 100 deletions(-) diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index d39fce60d32..c49845cb3c7 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -3713,7 +3713,10 @@ s3_CreatePresignedPost: - sdk_version: 4 github: dotnetv4/S3 excerpts: - - description: + - description: Create a presigned POST URL with conditions + snippet_tags: + - S3.dotnetv4.CreatePresignedPostAsync + - description: Example using presigned POST snippet_tags: - S3.dotnetv4.CreatePresignedPost services: diff --git a/dotnetv4/S3/Actions/S3Wrapper.cs b/dotnetv4/S3/Actions/S3Wrapper.cs index 3107b89305a..6592c206f8f 100644 --- a/dotnetv4/S3/Actions/S3Wrapper.cs +++ b/dotnetv4/S3/Actions/S3Wrapper.cs @@ -59,53 +59,6 @@ public async Task CreateBucketAsync(string bucketName) return bucketName; } - /// - /// Create a presigned POST URL for uploading a file to an S3 bucket. - /// - /// The name of the bucket. - /// The object key (path) where the uploaded file will be stored. - /// When the presigned URL expires. - /// A CreatePresignedPostResponse object with URL and form fields. - public async Task CreatePresignedPostAsync( - string bucketName, string objectKey, DateTime expires) - { - var request = new CreatePresignedPostRequest - { - BucketName = bucketName, - Key = objectKey, - Expires = expires - }; - - return await _s3Client.CreatePresignedPostAsync(request); - } - - /// - /// Create a presigned POST URL with custom fields. - /// - /// The name of the bucket. - /// The object key (path) where the uploaded file will be stored. - /// When the presigned URL expires. - /// Dictionary of fields to add to the form. - /// A CreatePresignedPostResponse object with URL and form fields. - public async Task CreatePresignedPostWithFieldsAsync( - string bucketName, string objectKey, DateTime expires, Dictionary fields) - { - var request = new CreatePresignedPostRequest - { - BucketName = bucketName, - Key = objectKey, - Expires = expires - }; - - // Add custom fields - foreach (var field in fields) - { - request.Fields.Add(field.Key, field.Value); - } - - return await _s3Client.CreatePresignedPostAsync(request); - } - /// /// Create a presigned POST URL with conditions. /// @@ -115,6 +68,7 @@ public async Task CreatePresignedPostWithFieldsAsyn /// Dictionary of fields to add to the form. /// List of conditions to apply. /// A CreatePresignedPostResponse object with URL and form fields. + // snippet-start:[S3.dotnetv4.CreatePresignedPostAsync] public async Task CreatePresignedPostWithConditionsAsync( string bucketName, string objectKey, DateTime expires, Dictionary? fields = null, List? conditions = null) @@ -146,26 +100,7 @@ public async Task CreatePresignedPostWithConditions return await _s3Client.CreatePresignedPostAsync(request); } - - /// - /// Upload a file to an S3 bucket. - /// - /// The name of the bucket. - /// The object key where the file will be stored. - /// The path to the file to upload. - /// The response from the PutObjectAsync call. - public async Task UploadFileAsync( - string bucketName, string objectKey, string filePath) - { - var request = new PutObjectRequest - { - BucketName = bucketName, - Key = objectKey, - FilePath = filePath - }; - - return await _s3Client.PutObjectAsync(request); - } + // snippet-end:[S3.dotnetv4.CreatePresignedPostAsync] /// /// Delete an object from an S3 bucket. @@ -222,35 +157,4 @@ public async Task GetObjectMetadataAsync( return await _s3Client.GetObjectMetadataAsync(request); } - - /// - /// Get an object from an S3 bucket. - /// - /// The name of the bucket. - /// The object key. - /// The response from the GetObjectAsync call. - public async Task GetObjectAsync( - string bucketName, string objectKey) - { - var request = new GetObjectRequest - { - BucketName = bucketName, - Key = objectKey - }; - - return await _s3Client.GetObjectAsync(request); - } - - /// - /// Read the content of an S3 object as a string. - /// - /// The name of the bucket. - /// The object key. - /// The object content as a string. - public async Task ReadObjectContentAsync(string bucketName, string objectKey) - { - var response = await GetObjectAsync(bucketName, objectKey); - using var reader = new StreamReader(response.ResponseStream); - return await reader.ReadToEndAsync(); - } } diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md index 62c560c1ff7..107b049f3e2 100644 --- a/dotnetv4/S3/README.md +++ b/dotnetv4/S3/README.md @@ -33,7 +33,7 @@ For prerequisites, see the [README](../README.md#Prerequisites) in the `dotnetv4 Code excerpts that show you how to call individual service functions. -- [CreatePresignedPost](Actions/CreatePresignedPost.cs#L6) +- [CreatePresignedPost](Actions/S3Wrapper.cs#L71) ### Scenarios From fc789b4c066516d5682a4358d7bbbf4f015bef13 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 13:31:34 -0400 Subject: [PATCH 08/21] Add gen ai tag --- .doc_gen/metadata/s3_metadata.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index c49845cb3c7..9abff1970b8 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -3714,9 +3714,11 @@ s3_CreatePresignedPost: github: dotnetv4/S3 excerpts: - description: Create a presigned POST URL with conditions + genai: most snippet_tags: - S3.dotnetv4.CreatePresignedPostAsync - description: Example using presigned POST + genai: most snippet_tags: - S3.dotnetv4.CreatePresignedPost services: @@ -3732,6 +3734,7 @@ s3_Scenario_CreatePresignedPost: github: dotnetv4/S3/Scenarios/S3_CreatePresignedPost excerpts: - description: + genai: most snippet_tags: - S3.dotnetv4.CreatePresignedPostScenario services: From b96731f05c51ad18bf075c88ce4b8f442ff4cc40 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 13:36:37 -0400 Subject: [PATCH 09/21] update names --- .doc_gen/metadata/s3_metadata.yaml | 2 +- dotnetv4/DotNetV4Examples.sln | 12 +++++------ dotnetv4/S3/README.md | 2 +- ...ignedPostScenario.csproj => Basics.csproj} | 0 ...enario.cs => CreatePresignedPostBasics.cs} | 21 ++++++++++++++----- .../S3_CreatePresignedPost/Program.cs | 4 ++-- dotnetv4/S3/Tests/S3Tests.csproj | 2 +- dotnetv4/S3/Tests/S3WrapperTests.cs | 4 ++-- 8 files changed, 29 insertions(+), 18 deletions(-) rename dotnetv4/S3/Scenarios/S3_CreatePresignedPost/{CreatePresignedPostScenario.csproj => Basics.csproj} (100%) rename dotnetv4/S3/Scenarios/S3_CreatePresignedPost/{CreatePresignedPostScenario.cs => CreatePresignedPostBasics.cs} (92%) diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index 9abff1970b8..229ae89fbb8 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -3736,6 +3736,6 @@ s3_Scenario_CreatePresignedPost: - description: genai: most snippet_tags: - - S3.dotnetv4.CreatePresignedPostScenario + - S3.dotnetv4.CreatePresignedPostBasics services: s3: {CreateBucket, CreatePresignedPost, GetObjectMetadata, DeleteBucket} diff --git a/dotnetv4/DotNetV4Examples.sln b/dotnetv4/DotNetV4Examples.sln index 2e0f7d6fbf3..27ee5d1e06e 100644 --- a/dotnetv4/DotNetV4Examples.sln +++ b/dotnetv4/DotNetV4Examples.sln @@ -157,7 +157,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Tests", "S3\Tests\S3Tests EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{A65C33EA-4F2E-DE85-7501-4389A2100813}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreatePresignedPostScenario", "S3\Scenarios\S3_CreatePresignedPost\CreatePresignedPostScenario.csproj", "{8DC31D9E-C744-9F54-F67F-D5A387F37BDC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "S3\Scenarios\S3_CreatePresignedPost\Basics.csproj", "{2B6F24A0-4569-E8A2-81B4-3925FA4F0320}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -381,10 +381,10 @@ Global {11497EB7-B702-B537-3CBE-BA2F4F85F313}.Debug|Any CPU.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 - {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8DC31D9E-C744-9F54-F67F-D5A387F37BDC}.Release|Any CPU.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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B6F24A0-4569-E8A2-81B4-3925FA4F0320}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -456,7 +456,7 @@ Global {C0B05982-E721-6989-AFB6-43D7B540248B} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} {11497EB7-B702-B537-3CBE-BA2F4F85F313} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} {A65C33EA-4F2E-DE85-7501-4389A2100813} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} - {8DC31D9E-C744-9F54-F67F-D5A387F37BDC} = {A65C33EA-4F2E-DE85-7501-4389A2100813} + {2B6F24A0-4569-E8A2-81B4-3925FA4F0320} = {A65C33EA-4F2E-DE85-7501-4389A2100813} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08502818-E8E1-4A91-A51C-4C8C8D4FF9CA} diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md index 107b049f3e2..61e43fef288 100644 --- a/dotnetv4/S3/README.md +++ b/dotnetv4/S3/README.md @@ -40,7 +40,7 @@ Code excerpts that show you how to call individual service functions. Code examples that show you how to accomplish a specific task by calling multiple functions within the same service. -- [](Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs) +- [](Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs) diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.csproj b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj similarity index 100% rename from dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.csproj rename to dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs similarity index 92% rename from dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs rename to dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs index b664136fd5f..a91542818d4 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostScenario.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -3,7 +3,7 @@ namespace S3Scenarios; -// snippet-start:[S3.dotnetv4.CreatePresignedPostScenario] +// snippet-start:[S3.dotnetv4.CreatePresignedPostBasics] /// /// Scenario demonstrating the complete workflow for presigned POST URLs: /// 1. Create an S3 bucket @@ -11,7 +11,7 @@ namespace S3Scenarios; /// 3. Upload a file using the presigned POST URL /// 4. Clean up resources /// -public class CreatePresignedPostScenario +public class CreatePresignedPostBasics { private readonly S3Wrapper _s3Wrapper; private readonly ILogger _logger; @@ -27,7 +27,7 @@ public class CreatePresignedPostScenario /// The logger to use. /// The UI methods to use. /// Whether to run in interactive mode. - public CreatePresignedPostScenario(S3Wrapper s3Wrapper, ILogger logger, UiMethods uiMethods, bool isInteractive) + public CreatePresignedPostBasics(S3Wrapper s3Wrapper, ILogger logger, UiMethods uiMethods, bool isInteractive) { _s3Wrapper = s3Wrapper; _logger = logger; @@ -120,7 +120,7 @@ private async Task CreatePresignedPostAsync() Console.WriteLine($"Creating presigned POST URL for {_bucketName}/{_objectKey}"); Console.WriteLine($"Expiration: {expiration} UTC"); - var response = await _s3Wrapper.CreatePresignedPostAsync(_bucketName!, _objectKey, expiration); + var response = await _s3Wrapper.CreatePresignedPostWithConditionsAsync(_bucketName!, _objectKey, expiration); Console.WriteLine("Successfully created presigned POST URL"); return response; @@ -251,5 +251,16 @@ private async Task CleanupAsync() } } } + + public static void DisplayPresignedPostFields(CreatePresignedPostResponse response) + { + Console.WriteLine($"Presigned POST URL: {response.Url}"); + Console.WriteLine("Form fields to include:"); + + foreach (var field in response.Fields) + { + Console.WriteLine($" {field.Key}: {field.Value}"); + } + } } -// snippet-end:[S3.dotnetv4.CreatePresignedPostScenario] +// snippet-end:[S3.dotnetv4.CreatePresignedPostBasics] diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs index 842ec2c3084..d1040d8eb9a 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs @@ -37,8 +37,8 @@ public static async Task Main(string[] args) var uiMethods = new UiMethods(); // Create the scenario instance - var logger = loggerFactory.CreateLogger(); - var scenario = new CreatePresignedPostScenario(s3Wrapper, logger, uiMethods, isInteractive); + var logger = loggerFactory.CreateLogger(); + var scenario = new CreatePresignedPostBasics(s3Wrapper, logger, uiMethods, isInteractive); // Run the scenario await scenario.RunAsync(); diff --git a/dotnetv4/S3/Tests/S3Tests.csproj b/dotnetv4/S3/Tests/S3Tests.csproj index b4cbb82748b..86b14e35ec4 100644 --- a/dotnetv4/S3/Tests/S3Tests.csproj +++ b/dotnetv4/S3/Tests/S3Tests.csproj @@ -26,6 +26,6 @@ - + diff --git a/dotnetv4/S3/Tests/S3WrapperTests.cs b/dotnetv4/S3/Tests/S3WrapperTests.cs index af486a79b80..70522b1ec03 100644 --- a/dotnetv4/S3/Tests/S3WrapperTests.cs +++ b/dotnetv4/S3/Tests/S3WrapperTests.cs @@ -21,7 +21,7 @@ public class S3WrapperTests public async Task TestScenario() { // Arrange. - var loggerScenarioMock = new Mock>(); + var loggerScenarioMock = new Mock>(); var loggerWrapperMock = new Mock>(); var uiMethods = new S3Scenarios.UiMethods(); bool isInteractive = false; @@ -29,7 +29,7 @@ public async Task TestScenario() _client = new AmazonS3Client(); _s3Wrapper = new S3Wrapper(_client, loggerWrapperMock.Object); - var scenario = new S3Scenarios.CreatePresignedPostScenario( + var scenario = new S3Scenarios.CreatePresignedPostBasics( _s3Wrapper, loggerScenarioMock.Object, uiMethods, From 0cd73220815140715605eab8e9c900ac3e7acb51 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 24 Jul 2025 17:20:11 -0400 Subject: [PATCH 10/21] update files --- dotnetv4/S3/Actions/CreatePresignedPost.cs | 186 ++++++++++++------ dotnetv4/S3/Actions/PresignedPostUtils.cs | 25 --- dotnetv4/S3/Actions/S3Wrapper.cs | 52 +---- dotnetv4/S3/S3Examples.sln | 21 +- .../CreatePresignedPostBasics.cs | 134 +++++++++---- .../S3_CreatePresignedPost/Program.cs | 46 ----- .../S3_CreatePresignedPost/Usings.cs | 3 +- ...s.cs => CreatePresignedPostBasicsTests.cs} | 24 +-- 8 files changed, 258 insertions(+), 233 deletions(-) delete mode 100644 dotnetv4/S3/Actions/PresignedPostUtils.cs delete mode 100644 dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs rename dotnetv4/S3/Tests/{S3WrapperTests.cs => CreatePresignedPostBasicsTests.cs} (75%) diff --git a/dotnetv4/S3/Actions/CreatePresignedPost.cs b/dotnetv4/S3/Actions/CreatePresignedPost.cs index ae37cac7fc7..3966a481de1 100644 --- a/dotnetv4/S3/Actions/CreatePresignedPost.cs +++ b/dotnetv4/S3/Actions/CreatePresignedPost.cs @@ -5,61 +5,10 @@ namespace S3Actions; // snippet-start:[S3.dotnetv4.CreatePresignedPost] /// -/// Demonstrates how to create Amazon S3 presigned POST URLs with both conditions and filename variables. -/// This example shows how to add restrictions to uploads and preserve original filenames. +/// Class for creating Amazon S3 presigned POST URLs with various conditions. /// public class CreatePresignedPost { - /// - /// Create a presigned POST URL with both filename variable and conditions. - /// - /// The S3Wrapper instance to use. - /// The logger to use. - /// The name of the bucket. - /// The prefix for the key, final key will be prefix + actual filename. - /// A CreatePresignedPostResponse containing the URL and form fields. - public static async Task CreateWithFilenameAndConditions( - S3Wrapper s3Wrapper, - ILogger logger, - string bucketName, - string keyPrefix) - { - var expiration = DateTime.UtcNow.AddHours(1); - - // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename - string objectKey = keyPrefix + "${filename}"; - - // Add custom metadata and fields - var fields = new Dictionary - { - // Add a custom metadata field - { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, - - // Return HTTP 201 on successful upload - { "success_action_status", "201" }, - - // Set the content type - { "Content-Type", "text/plain" } - }; - - // Add policy conditions - var conditions = new List - { - // File size must be between 1 byte and 1 MB - S3PostCondition.ContentLengthRange(1, 1048576) - }; - - logger.LogInformation("Creating presigned POST URL with filename variable and conditions for {bucket}/{key}", - bucketName, objectKey); - - var response = await s3Wrapper.CreatePresignedPostWithConditionsAsync( - bucketName, objectKey, expiration, fields, conditions); - - logger.LogInformation("Successfully created presigned POST URL with filename variable and conditions"); - - return response; - } - /// /// Main method that demonstrates creating and using presigned POST URLs with combined features. /// @@ -71,7 +20,6 @@ public static async Task Main(string[] args) using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) .ConfigureServices((_, services) => services.AddAWSService() - .AddTransient() ) .Build(); @@ -79,10 +27,7 @@ public static async Task Main(string[] args) var s3Client = host.Services.GetRequiredService(); var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); var logger = loggerFactory.CreateLogger(); - - // Create the wrapper instance - var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); - + Console.WriteLine("Amazon S3 CreatePresignedPost Example"); Console.WriteLine("==================================="); @@ -97,11 +42,11 @@ public static async Task Main(string[] args) // Generate the presigned POST URL with combined features Console.WriteLine("\nCreating a presigned POST URL with both filename preservation and upload restrictions..."); - var response = await CreateWithFilenameAndConditions(s3Wrapper, logger, bucketName, keyPrefix); - + var response = await CreateWithFilenameAndConditions(s3Client, logger, bucketName, keyPrefix); + // Display the URL and fields Console.WriteLine("\nPresigned POST URL with combined features created successfully:"); - PresignedPostUtils.DisplayPresignedPostFields(response); + DisplayPresignedPostFields(response); Console.WriteLine("\nThis example combines multiple features:"); Console.WriteLine(" • Uses ${filename} to preserve the original filename in the 'uploads/' prefix"); @@ -109,7 +54,7 @@ public static async Task Main(string[] args) Console.WriteLine(" • Sets success_action_status to return HTTP 201 on success"); Console.WriteLine(" • Restricts content type to text/plain"); Console.WriteLine(" • Limits file size to between 1 byte and 1 MB"); - + Console.WriteLine("\nExample completed successfully."); } catch (AmazonS3Exception ex) @@ -121,5 +66,124 @@ public static async Task Main(string[] args) Console.WriteLine($"Error: {ex.Message}"); } } + + /// + /// Create a presigned POST URL with both filename variable and conditions. + /// + /// The Amazon S3 client. + /// The logger to use. + /// The name of the bucket. + /// The prefix for the key, final key will be prefix + actual filename. + /// A CreatePresignedPostResponse containing the URL and form fields. + // snippet-start:[S3.dotnetv4.CreateWithFilenameAndConditions] + public static async Task CreateWithFilenameAndConditions( + IAmazonS3 s3Client, + ILogger logger, + string bucketName, + string keyPrefix) + { + var expiration = DateTime.UtcNow.AddHours(1); + + // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename + string objectKey = keyPrefix + "${filename}"; + + // Add custom metadata and fields + var fields = new Dictionary + { + // Add a custom metadata field + { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, + + // Return HTTP 201 on successful upload + { "success_action_status", "201" }, + + // Set the content type + { "Content-Type", "text/plain" } + }; + + // Add policy conditions + var conditions = new List + { + // File size must be between 1 byte and 1 MB + S3PostCondition.ContentLengthRange(1, 1048576) + }; + + logger.LogInformation("Creating presigned POST URL with filename variable and conditions for {bucket}/{key}", + bucketName, objectKey); + + var response = await CreatePresignedPostAsync( + s3Client, logger, bucketName, objectKey, expiration, fields, conditions); + + logger.LogInformation("Successfully created presigned POST URL with filename variable and conditions"); + + return response; + } + // snippet-end:[S3.dotnetv4.CreateWithFilenameAndConditions] + + /// + /// Create a presigned POST URL with conditions. + /// + /// The Amazon S3 client. + /// The logger to use. + /// The name of the bucket. + /// The object key (path) where the uploaded file will be stored. + /// When the presigned URL expires. + /// Dictionary of fields to add to the form. + /// List of conditions to apply. + /// A CreatePresignedPostResponse object with URL and form fields. + // snippet-start:[S3.dotnetv4.CreatePresignedPostAsync] + public static async Task CreatePresignedPostAsync( + IAmazonS3 s3Client, + ILogger logger, + string bucketName, + string objectKey, + DateTime expires, + Dictionary? fields = null, + List? conditions = null) + { + var request = new CreatePresignedPostRequest + { + BucketName = bucketName, + Key = objectKey, + Expires = expires + }; + + // Add custom fields if provided + if (fields != null) + { + foreach (var field in fields) + { + request.Fields.Add(field.Key, field.Value); + } + } + + // Add conditions if provided + if (conditions != null) + { + foreach (var condition in conditions) + { + request.Conditions.Add(condition); + } + } + + return await s3Client.CreatePresignedPostAsync(request); + } + // snippet-end:[S3.dotnetv4.CreatePresignedPostAsync] + + /// + /// Display the fields from a presigned POST response. + /// + /// The CreatePresignedPostResponse to display. + // snippet-start:[S3.dotnetv4.DisplayPresignedPostFields] + public static void DisplayPresignedPostFields(CreatePresignedPostResponse response) + { + Console.WriteLine($"Presigned POST URL: {response.Url}"); + Console.WriteLine("Form fields to include:"); + + foreach (var field in response.Fields) + { + Console.WriteLine($" {field.Key}: {field.Value}"); + } + } + // snippet-end:[S3.dotnetv4.DisplayPresignedPostFields] } // snippet-end:[S3.dotnetv4.CreatePresignedPost] diff --git a/dotnetv4/S3/Actions/PresignedPostUtils.cs b/dotnetv4/S3/Actions/PresignedPostUtils.cs deleted file mode 100644 index 3827cbdfb45..00000000000 --- a/dotnetv4/S3/Actions/PresignedPostUtils.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace S3Actions; - -/// -/// Utility methods for working with presigned POST URLs. -/// -public static class PresignedPostUtils -{ - /// - /// Display the fields from a presigned POST response. - /// - /// The CreatePresignedPostResponse to display. - public static void DisplayPresignedPostFields(CreatePresignedPostResponse response) - { - Console.WriteLine($"Presigned POST URL: {response.Url}"); - Console.WriteLine("Form fields to include:"); - - foreach (var field in response.Fields) - { - Console.WriteLine($" {field.Key}: {field.Value}"); - } - } -} diff --git a/dotnetv4/S3/Actions/S3Wrapper.cs b/dotnetv4/S3/Actions/S3Wrapper.cs index 6592c206f8f..88db04147b8 100644 --- a/dotnetv4/S3/Actions/S3Wrapper.cs +++ b/dotnetv4/S3/Actions/S3Wrapper.cs @@ -23,6 +23,15 @@ public S3Wrapper(IAmazonS3 s3Client, ILogger logger) _logger = logger; } + /// + /// Get the Amazon S3 client. + /// + /// The Amazon S3 client. + public IAmazonS3 GetS3Client() + { + return _s3Client; + } + /// /// Create a bucket and wait until it's ready to use. /// @@ -59,49 +68,6 @@ public async Task CreateBucketAsync(string bucketName) return bucketName; } - /// - /// Create a presigned POST URL with conditions. - /// - /// The name of the bucket. - /// The object key (path) where the uploaded file will be stored. - /// When the presigned URL expires. - /// Dictionary of fields to add to the form. - /// List of conditions to apply. - /// A CreatePresignedPostResponse object with URL and form fields. - // snippet-start:[S3.dotnetv4.CreatePresignedPostAsync] - public async Task CreatePresignedPostWithConditionsAsync( - string bucketName, string objectKey, DateTime expires, - Dictionary? fields = null, List? conditions = null) - { - var request = new CreatePresignedPostRequest - { - BucketName = bucketName, - Key = objectKey, - Expires = expires - }; - - // Add custom fields if provided - if (fields != null) - { - foreach (var field in fields) - { - request.Fields.Add(field.Key, field.Value); - } - } - - // Add conditions if provided - if (conditions != null) - { - foreach (var condition in conditions) - { - request.Conditions.Add(condition); - } - } - - return await _s3Client.CreatePresignedPostAsync(request); - } - // snippet-end:[S3.dotnetv4.CreatePresignedPostAsync] - /// /// Delete an object from an S3 bucket. /// diff --git a/dotnetv4/S3/S3Examples.sln b/dotnetv4/S3/S3Examples.sln index fedd08c6f7b..8bba441e593 100644 --- a/dotnetv4/S3/S3Examples.sln +++ b/dotnetv4/S3/S3Examples.sln @@ -8,10 +8,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Actions", "Actions\S3Acti EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{C13DDD1A-438D-4E52-90FB-A496A54516C7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreatePresignedPostScenario", "Scenarios\S3_CreatePresignedPost\CreatePresignedPostScenario.csproj", "{9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C51625C8-3B42-4810-BF1B-0E3C6C716FA6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basics", "Scenarios\S3_CreatePresignedPost\Basics.csproj", "{22C217CC-E2D9-9F79-EE15-24F9D262655E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Tests", "Tests\S3Tests.csproj", "{FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -22,17 +24,22 @@ Global {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Debug|Any CPU.Build.0 = Debug|Any CPU {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Release|Any CPU.ActiveCfg = Release|Any CPU {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Release|Any CPU.Build.0 = Release|Any CPU - {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B}.Release|Any CPU.Build.0 = Release|Any CPU + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Release|Any CPU.Build.0 = Release|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85} = {863BD09D-4F48-4BFF-B5E7-941503A41F0A} - {9A8F38DD-5C8F-4A45-B45C-5A189A030C9B} = {C13DDD1A-438D-4E52-90FB-A496A54516C7} + {22C217CC-E2D9-9F79-EE15-24F9D262655E} = {C13DDD1A-438D-4E52-90FB-A496A54516C7} + {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C} = {C51625C8-3B42-4810-BF1B-0E3C6C716FA6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5ACB5B08-E8F8-453C-B63B-6C0C9DE67780} diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs index a91542818d4..2098cc14ecf 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +using Microsoft.Extensions.Hosting; + namespace S3Scenarios; // snippet-start:[S3.dotnetv4.CreatePresignedPostBasics] @@ -13,34 +15,50 @@ namespace S3Scenarios; /// public class CreatePresignedPostBasics { - private readonly S3Wrapper _s3Wrapper; - private readonly ILogger _logger; - private readonly UiMethods _uiMethods; - private readonly bool _isInteractive; - private string? _bucketName; - private string? _objectKey; + public static ILogger _logger = null!; + public static S3Wrapper _s3Wrapper = null!; + public static UiMethods _uiMethods = null!; + public static bool _isInteractive = true; + public static string? _bucketName; + public static string? _objectKey; /// - /// Constructor for the scenario class. + /// Set up the services and logging. /// - /// The S3Wrapper instance to use. - /// The logger to use. - /// The UI methods to use. - /// Whether to run in interactive mode. - public CreatePresignedPostBasics(S3Wrapper s3Wrapper, ILogger logger, UiMethods uiMethods, bool isInteractive) + /// The IHost instance. + public static void SetUpServices(IHost host) { - _s3Wrapper = s3Wrapper; - _logger = logger; - _uiMethods = uiMethods; - _isInteractive = isInteractive; + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + }); + _logger = new Logger(loggerFactory); + + // Now the client is available for injection. + _s3Wrapper = host.Services.GetRequiredService(); + _uiMethods = new UiMethods(); } /// - /// Run the scenario steps. + /// Perform the actions defined for the Amazon S3 Presigned POST scenario. /// - /// A Task representing the asynchronous operation. - public async Task RunAsync() + /// Command line arguments. + /// A Task object. + public static async Task Main(string[] args) { + // Check for non-interactive mode + _isInteractive = !args.Contains("--non-interactive"); + + // Set up dependency injection for Amazon S3 + using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) + .ConfigureServices((_, services) => + services.AddAWSService() + .AddTransient() + ) + .Build(); + + SetUpServices(host); + try { // Display overview @@ -58,7 +76,7 @@ public async Task RunAsync() // Step 3: Display URL and fields _uiMethods.DisplayTitle("Step 3: Presigned POST URL details"); - PresignedPostUtils.DisplayPresignedPostFields(response); + CreatePresignedPost.DisplayPresignedPostFields(response); _uiMethods.PressEnter(_isInteractive); // Step 4: Upload file @@ -95,7 +113,7 @@ public async Task RunAsync() /// /// Create an S3 bucket for the scenario. /// - private async Task CreateBucketAsync() + private static async Task CreateBucketAsync() { _uiMethods.DisplayTitle("Step 1: Create an S3 bucket"); @@ -109,10 +127,58 @@ private async Task CreateBucketAsync() Console.WriteLine($"Successfully created bucket: {_bucketName}"); } + /// + /// Create a presigned POST URL with conditions. + /// + /// The Amazon S3 client. + /// The name of the bucket. + /// The object key (path) where the uploaded file will be stored. + /// When the presigned URL expires. + /// Dictionary of fields to add to the form. + /// List of conditions to apply. + /// A CreatePresignedPostResponse object with URL and form fields. + // snippet-start:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] + private static async Task CreatePresignedPostAsync( + IAmazonS3 s3Client, + string bucketName, + string objectKey, + DateTime expires, + Dictionary? fields = null, + List? conditions = null) + { + var request = new CreatePresignedPostRequest + { + BucketName = bucketName, + Key = objectKey, + Expires = expires + }; + + // Add custom fields if provided + if (fields != null) + { + foreach (var field in fields) + { + request.Fields.Add(field.Key, field.Value); + } + } + + // Add conditions if provided + if (conditions != null) + { + foreach (var condition in conditions) + { + request.Conditions.Add(condition); + } + } + + return await s3Client.CreatePresignedPostAsync(request); + } + // snippet-end:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] + /// /// Create a presigned POST URL. /// - private async Task CreatePresignedPostAsync() + private static async Task CreatePresignedPostAsync() { _objectKey = "example-upload.txt"; var expiration = DateTime.UtcNow.AddMinutes(10); // Short expiration for the demo @@ -120,7 +186,11 @@ private async Task CreatePresignedPostAsync() Console.WriteLine($"Creating presigned POST URL for {_bucketName}/{_objectKey}"); Console.WriteLine($"Expiration: {expiration} UTC"); - var response = await _s3Wrapper.CreatePresignedPostWithConditionsAsync(_bucketName!, _objectKey, expiration); + // Get S3 client from S3Wrapper + var s3Client = _s3Wrapper.GetS3Client(); + + var response = await CreatePresignedPostAsync( + s3Client, _bucketName!, _objectKey, expiration); Console.WriteLine("Successfully created presigned POST URL"); return response; @@ -129,7 +199,7 @@ private async Task CreatePresignedPostAsync() /// /// Upload a file using the presigned POST URL. /// - private async Task UploadFileAsync(CreatePresignedPostResponse response) + private static async Task UploadFileAsync(CreatePresignedPostResponse response) { // Create a temporary test file to upload @@ -164,7 +234,7 @@ private async Task UploadFileAsync(CreatePresignedPostResponse response) /// /// Helper method to upload a file using a presigned POST URL. /// - private async Task<(bool Success, HttpStatusCode StatusCode, string Response)> UploadFileWithPresignedPostAsync( + private static async Task<(bool Success, HttpStatusCode StatusCode, string Response)> UploadFileWithPresignedPostAsync( CreatePresignedPostResponse response, string filePath) { @@ -208,7 +278,7 @@ private async Task UploadFileAsync(CreatePresignedPostResponse response) /// /// Verify that the uploaded file exists in the S3 bucket. /// - private async Task VerifyFileExistsAsync() + private static async Task VerifyFileExistsAsync() { _uiMethods.DisplayTitle("Step 5: Verify uploaded file exists"); @@ -233,7 +303,7 @@ private async Task VerifyFileExistsAsync() /// /// Clean up resources created by the scenario. /// - private async Task CleanupAsync() + private static async Task CleanupAsync() { if (!string.IsNullOrEmpty(_bucketName)) @@ -252,15 +322,5 @@ private async Task CleanupAsync() } } - public static void DisplayPresignedPostFields(CreatePresignedPostResponse response) - { - Console.WriteLine($"Presigned POST URL: {response.Url}"); - Console.WriteLine("Form fields to include:"); - - foreach (var field in response.Fields) - { - Console.WriteLine($" {field.Key}: {field.Value}"); - } - } } // snippet-end:[S3.dotnetv4.CreatePresignedPostBasics] diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs deleted file mode 100644 index d1040d8eb9a..00000000000 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace S3Scenarios; - -/// -/// Main entry point for the scenario program. -/// -public class Program -{ - /// - /// Main method that sets up the services and runs the scenario. - /// - /// Command line arguments. Use --non-interactive to run in non-interactive mode. - /// Async task. - public static async Task Main(string[] args) - { - // Check for non-interactive mode - bool isInteractive = !args.Contains("--non-interactive"); - - // Set up dependency injection for Amazon S3 - using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureServices((_, services) => - services.AddAWSService() - .AddTransient() - ) - .Build(); - - // Get the services - var s3Client = host.Services.GetRequiredService(); - var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - - // Create the wrapper instance - var s3Wrapper = new S3Wrapper(s3Client, loggerFactory.CreateLogger()); - - // Create UI methods - var uiMethods = new UiMethods(); - - // Create the scenario instance - var logger = loggerFactory.CreateLogger(); - var scenario = new CreatePresignedPostBasics(s3Wrapper, logger, uiMethods, isInteractive); - - // Run the scenario - await scenario.RunAsync(); - } -} diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs index 45c843f8080..0e36810b5cb 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs @@ -7,11 +7,10 @@ global using System.Net; global using System.Net.Http; global using System.Net.Http.Headers; -global using System.Text; global using System.Threading.Tasks; global using Amazon.S3; global using Amazon.S3.Model; -global using Amazon.S3.Util; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.DependencyInjection; global using S3Actions; + diff --git a/dotnetv4/S3/Tests/S3WrapperTests.cs b/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs similarity index 75% rename from dotnetv4/S3/Tests/S3WrapperTests.cs rename to dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs index 70522b1ec03..17a61f68911 100644 --- a/dotnetv4/S3/Tests/S3WrapperTests.cs +++ b/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs @@ -5,15 +5,15 @@ namespace S3Tests; /// /// Integration tests for the Amazon Simple Storage Service (Amazon S3) -/// presigned POST URL scenario. +/// presigned POST URL scenario (CreatePresignedPostBasics). /// -public class S3WrapperTests +public class CreatePresignedPostBasicsTests { private AmazonS3Client _client = null!; private S3Wrapper _s3Wrapper = null!; /// - /// Verifies the scenario with an integration test. No errors should be logged. + /// Verifies the presigned POST URL scenario with an integration test. No errors should be logged. /// /// Async task. [Fact] @@ -24,17 +24,16 @@ public async Task TestScenario() var loggerScenarioMock = new Mock>(); var loggerWrapperMock = new Mock>(); var uiMethods = new S3Scenarios.UiMethods(); - bool isInteractive = false; - + _client = new AmazonS3Client(); _s3Wrapper = new S3Wrapper(_client, loggerWrapperMock.Object); - var scenario = new S3Scenarios.CreatePresignedPostBasics( - _s3Wrapper, - loggerScenarioMock.Object, - uiMethods, - isInteractive); - + // Set up the static fields directly + S3Scenarios.CreatePresignedPostBasics._logger = loggerScenarioMock.Object; + S3Scenarios.CreatePresignedPostBasics._s3Wrapper = _s3Wrapper; + S3Scenarios.CreatePresignedPostBasics._uiMethods = uiMethods; + S3Scenarios.CreatePresignedPostBasics._isInteractive = false; + // Set up verification for error logging. loggerScenarioMock.Setup(logger => logger.Log( It.Is(logLevel => logLevel == LogLevel.Error), @@ -53,7 +52,8 @@ public async Task TestScenario() )); // Act. - await scenario.RunAsync(); + // Call the static Main method with --non-interactive flag to match previous behavior + await S3Scenarios.CreatePresignedPostBasics.Main(new string[] { "--non-interactive" }); // Assert no exceptions or errors logged. loggerScenarioMock.Verify( From 14002c89c1aa2ece92dff892a08bd04badab3b54 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Fri, 25 Jul 2025 11:32:10 -0400 Subject: [PATCH 11/21] update metadata --- .doc_gen/metadata/s3_metadata.yaml | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index 229ae89fbb8..a757ee17a7a 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -2776,6 +2776,12 @@ s3_Scenario_PresignedUrl: - description: Generate a presigned URL and perform an upload using that URL. snippet_tags: - S3.dotnetv3.UploadUsingPresignedURLExample + - sdk_version: 4 + github: dotnetv4/S3/Scenarios/S3_CreatePresignedPost + excerpts: + - description: Create and use presigned POST URLs for direct browser uploads. + snippet_tags: + - S3.dotnetv4.CreatePresignedPostBasics Java: versions: - sdk_version: 2 @@ -3713,29 +3719,13 @@ s3_CreatePresignedPost: - sdk_version: 4 github: dotnetv4/S3 excerpts: - - description: Create a presigned POST URL with conditions + - description: Create a presigned POST URL genai: most snippet_tags: - S3.dotnetv4.CreatePresignedPostAsync - - description: Example using presigned POST + - description: Example using presigned POST URL genai: most snippet_tags: - S3.dotnetv4.CreatePresignedPost services: s3: {CreatePresignedPost} - -s3_Scenario_CreatePresignedPost: - title: Create and use presigned POST URLs - category: Scenarios - languages: - .NET: - versions: - - sdk_version: 4 - github: dotnetv4/S3/Scenarios/S3_CreatePresignedPost - excerpts: - - description: - genai: most - snippet_tags: - - S3.dotnetv4.CreatePresignedPostBasics - services: - s3: {CreateBucket, CreatePresignedPost, GetObjectMetadata, DeleteBucket} From a413e7e213fc5421ad1ff5892883730a4c38aef9 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Fri, 25 Jul 2025 17:10:39 -0400 Subject: [PATCH 12/21] Fix snippets --- .doc_gen/metadata/s3_metadata.yaml | 4 - dotnetv4/S3/Actions/CreatePresignedPost.cs | 88 ++++++++-------------- 2 files changed, 31 insertions(+), 61 deletions(-) diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index a757ee17a7a..e798f94e0b1 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -3720,10 +3720,6 @@ s3_CreatePresignedPost: github: dotnetv4/S3 excerpts: - description: Create a presigned POST URL - genai: most - snippet_tags: - - S3.dotnetv4.CreatePresignedPostAsync - - description: Example using presigned POST URL genai: most snippet_tags: - S3.dotnetv4.CreatePresignedPost diff --git a/dotnetv4/S3/Actions/CreatePresignedPost.cs b/dotnetv4/S3/Actions/CreatePresignedPost.cs index 3966a481de1..9a96bbbbba8 100644 --- a/dotnetv4/S3/Actions/CreatePresignedPost.cs +++ b/dotnetv4/S3/Actions/CreatePresignedPost.cs @@ -39,10 +39,40 @@ public static async Task Main(string[] args) // Create a key prefix for this example string keyPrefix = "uploads/"; + var expiration = DateTime.UtcNow.AddHours(1); + + // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename + string objectKey = keyPrefix + "${filename}"; + + // Add custom metadata and fields + var fields = new Dictionary + { + // Add a custom metadata field + { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, + + // Return HTTP 201 on successful upload + { "success_action_status", "201" }, + + // Set the content type + { "Content-Type", "text/plain" } + }; + + // Add policy conditions + var conditions = new List + { + // File size must be between 1 byte and 1 MB + S3PostCondition.ContentLengthRange(1, 1048576) + }; // Generate the presigned POST URL with combined features Console.WriteLine("\nCreating a presigned POST URL with both filename preservation and upload restrictions..."); - var response = await CreateWithFilenameAndConditions(s3Client, logger, bucketName, keyPrefix); + logger.LogInformation("Creating presigned POST URL with filename variable and conditions for {bucket}/{key}", + bucketName, objectKey); + + var response = await CreatePresignedPostAsync( + s3Client, logger, bucketName, objectKey, expiration, fields, conditions); + + logger.LogInformation("Successfully created presigned POST URL with filename variable and conditions"); // Display the URL and fields Console.WriteLine("\nPresigned POST URL with combined features created successfully:"); @@ -66,58 +96,6 @@ public static async Task Main(string[] args) Console.WriteLine($"Error: {ex.Message}"); } } - - /// - /// Create a presigned POST URL with both filename variable and conditions. - /// - /// The Amazon S3 client. - /// The logger to use. - /// The name of the bucket. - /// The prefix for the key, final key will be prefix + actual filename. - /// A CreatePresignedPostResponse containing the URL and form fields. - // snippet-start:[S3.dotnetv4.CreateWithFilenameAndConditions] - public static async Task CreateWithFilenameAndConditions( - IAmazonS3 s3Client, - ILogger logger, - string bucketName, - string keyPrefix) - { - var expiration = DateTime.UtcNow.AddHours(1); - - // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename - string objectKey = keyPrefix + "${filename}"; - - // Add custom metadata and fields - var fields = new Dictionary - { - // Add a custom metadata field - { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, - - // Return HTTP 201 on successful upload - { "success_action_status", "201" }, - - // Set the content type - { "Content-Type", "text/plain" } - }; - - // Add policy conditions - var conditions = new List - { - // File size must be between 1 byte and 1 MB - S3PostCondition.ContentLengthRange(1, 1048576) - }; - - logger.LogInformation("Creating presigned POST URL with filename variable and conditions for {bucket}/{key}", - bucketName, objectKey); - - var response = await CreatePresignedPostAsync( - s3Client, logger, bucketName, objectKey, expiration, fields, conditions); - - logger.LogInformation("Successfully created presigned POST URL with filename variable and conditions"); - - return response; - } - // snippet-end:[S3.dotnetv4.CreateWithFilenameAndConditions] /// /// Create a presigned POST URL with conditions. @@ -130,7 +108,6 @@ public static async Task CreateWithFilenameAndCondi /// Dictionary of fields to add to the form. /// List of conditions to apply. /// A CreatePresignedPostResponse object with URL and form fields. - // snippet-start:[S3.dotnetv4.CreatePresignedPostAsync] public static async Task CreatePresignedPostAsync( IAmazonS3 s3Client, ILogger logger, @@ -167,13 +144,11 @@ public static async Task CreatePresignedPostAsync( return await s3Client.CreatePresignedPostAsync(request); } - // snippet-end:[S3.dotnetv4.CreatePresignedPostAsync] /// /// Display the fields from a presigned POST response. /// /// The CreatePresignedPostResponse to display. - // snippet-start:[S3.dotnetv4.DisplayPresignedPostFields] public static void DisplayPresignedPostFields(CreatePresignedPostResponse response) { Console.WriteLine($"Presigned POST URL: {response.Url}"); @@ -184,6 +159,5 @@ public static void DisplayPresignedPostFields(CreatePresignedPostResponse respon Console.WriteLine($" {field.Key}: {field.Value}"); } } - // snippet-end:[S3.dotnetv4.DisplayPresignedPostFields] } // snippet-end:[S3.dotnetv4.CreatePresignedPost] From 47df32f11e4b22255b367628116e9a9ce381f20b Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 12:30:57 -0400 Subject: [PATCH 13/21] pr comments --- .doc_gen/metadata/s3_metadata.yaml | 2 +- dotnetv4/DotNetV4Examples.sln | 7 - dotnetv4/S3/Actions/CreatePresignedPost.cs | 163 ------------------ dotnetv4/S3/Actions/S3Actions.csproj | 17 -- dotnetv4/S3/Actions/Usings.cs | 16 -- .../S3_CreatePresignedPost/Basics.csproj | 6 +- .../CreatePresignedPostBasics.cs | 79 +++------ .../S3_CreatePresignedPost}/S3Wrapper.cs | 62 ++++++- .../S3_CreatePresignedPost/UiMethods.cs | 22 +++ .../S3_CreatePresignedPost/Usings.cs | 7 +- dotnetv4/S3/Tests/S3Tests.csproj | 3 +- dotnetv4/S3/Tests/Usings.cs | 3 +- 12 files changed, 110 insertions(+), 277 deletions(-) delete mode 100644 dotnetv4/S3/Actions/CreatePresignedPost.cs delete mode 100644 dotnetv4/S3/Actions/S3Actions.csproj delete mode 100644 dotnetv4/S3/Actions/Usings.cs rename dotnetv4/S3/{Actions => Scenarios/S3_CreatePresignedPost}/S3Wrapper.cs (68%) diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index e798f94e0b1..fee6960bc07 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -3722,6 +3722,6 @@ s3_CreatePresignedPost: - description: Create a presigned POST URL genai: most snippet_tags: - - S3.dotnetv4.CreatePresignedPost + - S3.dotnetv4.Scenario_CreatePresignedPostAsync services: s3: {CreatePresignedPost} diff --git a/dotnetv4/DotNetV4Examples.sln b/dotnetv4/DotNetV4Examples.sln index 27ee5d1e06e..8b243f70231 100644 --- a/dotnetv4/DotNetV4Examples.sln +++ b/dotnetv4/DotNetV4Examples.sln @@ -151,8 +151,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoDBActions", "DynamoDB EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "S3", "S3", "{F929DB74-DD0E-B0EF-AA66-D8703D547BBD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Actions", "S3\Actions\S3Actions.csproj", "{C0B05982-E721-6989-AFB6-43D7B540248B}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Tests", "S3\Tests\S3Tests.csproj", "{11497EB7-B702-B537-3CBE-BA2F4F85F313}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{A65C33EA-4F2E-DE85-7501-4389A2100813}" @@ -373,10 +371,6 @@ Global {B0F91FE2-6AC5-4FA8-B321-54623A516D4D}.Debug|Any CPU.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 - {C0B05982-E721-6989-AFB6-43D7B540248B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0B05982-E721-6989-AFB6-43D7B540248B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0B05982-E721-6989-AFB6-43D7B540248B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0B05982-E721-6989-AFB6-43D7B540248B}.Release|Any CPU.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}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -453,7 +447,6 @@ Global {F578CA07-E74F-4F47-9203-C67777D9BB78} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} {E10920BB-6409-41BB-9A9D-813BC37CC3C0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {B0F91FE2-6AC5-4FA8-B321-54623A516D4D} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} - {C0B05982-E721-6989-AFB6-43D7B540248B} = {F929DB74-DD0E-B0EF-AA66-D8703D547BBD} {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} diff --git a/dotnetv4/S3/Actions/CreatePresignedPost.cs b/dotnetv4/S3/Actions/CreatePresignedPost.cs deleted file mode 100644 index 9a96bbbbba8..00000000000 --- a/dotnetv4/S3/Actions/CreatePresignedPost.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -namespace S3Actions; - -// snippet-start:[S3.dotnetv4.CreatePresignedPost] -/// -/// Class for creating Amazon S3 presigned POST URLs with various conditions. -/// -public class CreatePresignedPost -{ - /// - /// Main method that demonstrates creating and using presigned POST URLs with combined features. - /// - /// Command line arguments. Not used in this example. - /// Async task. - public static async Task Main(string[] args) - { - // Set up dependency injection for Amazon S3 - using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureServices((_, services) => - services.AddAWSService() - ) - .Build(); - - // Get the services - var s3Client = host.Services.GetRequiredService(); - var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - var logger = loggerFactory.CreateLogger(); - - Console.WriteLine("Amazon S3 CreatePresignedPost Example"); - Console.WriteLine("==================================="); - - try - { - const string bucketName = "amzn-s3-demo-bucket"; - Console.WriteLine($"Using bucket: {bucketName}"); - Console.WriteLine("Note: You must have an existing bucket with this name or create one first."); - - // Create a key prefix for this example - string keyPrefix = "uploads/"; - var expiration = DateTime.UtcNow.AddHours(1); - - // Using "${filename}" placeholder in the key lets the browser replace it with the actual filename - string objectKey = keyPrefix + "${filename}"; - - // Add custom metadata and fields - var fields = new Dictionary - { - // Add a custom metadata field - { "x-amz-meta-uploaded-by", "dotnet-sdk-example" }, - - // Return HTTP 201 on successful upload - { "success_action_status", "201" }, - - // Set the content type - { "Content-Type", "text/plain" } - }; - - // Add policy conditions - var conditions = new List - { - // File size must be between 1 byte and 1 MB - S3PostCondition.ContentLengthRange(1, 1048576) - }; - - // Generate the presigned POST URL with combined features - Console.WriteLine("\nCreating a presigned POST URL with both filename preservation and upload restrictions..."); - logger.LogInformation("Creating presigned POST URL with filename variable and conditions for {bucket}/{key}", - bucketName, objectKey); - - var response = await CreatePresignedPostAsync( - s3Client, logger, bucketName, objectKey, expiration, fields, conditions); - - logger.LogInformation("Successfully created presigned POST URL with filename variable and conditions"); - - // Display the URL and fields - Console.WriteLine("\nPresigned POST URL with combined features created successfully:"); - DisplayPresignedPostFields(response); - - Console.WriteLine("\nThis example combines multiple features:"); - Console.WriteLine(" • Uses ${filename} to preserve the original filename in the 'uploads/' prefix"); - Console.WriteLine(" • Adds custom metadata (x-amz-meta-uploaded-by)"); - Console.WriteLine(" • Sets success_action_status to return HTTP 201 on success"); - Console.WriteLine(" • Restricts content type to text/plain"); - Console.WriteLine(" • Limits file size to between 1 byte and 1 MB"); - - Console.WriteLine("\nExample completed successfully."); - } - catch (AmazonS3Exception ex) - { - Console.WriteLine($"Amazon S3 error: {ex.Message}"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - } - - /// - /// Create a presigned POST URL with conditions. - /// - /// The Amazon S3 client. - /// The logger to use. - /// The name of the bucket. - /// The object key (path) where the uploaded file will be stored. - /// When the presigned URL expires. - /// Dictionary of fields to add to the form. - /// List of conditions to apply. - /// A CreatePresignedPostResponse object with URL and form fields. - public static async Task CreatePresignedPostAsync( - IAmazonS3 s3Client, - ILogger logger, - string bucketName, - string objectKey, - DateTime expires, - Dictionary? fields = null, - List? conditions = null) - { - var request = new CreatePresignedPostRequest - { - BucketName = bucketName, - Key = objectKey, - Expires = expires - }; - - // Add custom fields if provided - if (fields != null) - { - foreach (var field in fields) - { - request.Fields.Add(field.Key, field.Value); - } - } - - // Add conditions if provided - if (conditions != null) - { - foreach (var condition in conditions) - { - request.Conditions.Add(condition); - } - } - - return await s3Client.CreatePresignedPostAsync(request); - } - - /// - /// Display the fields from a presigned POST response. - /// - /// The CreatePresignedPostResponse to display. - public static void DisplayPresignedPostFields(CreatePresignedPostResponse response) - { - Console.WriteLine($"Presigned POST URL: {response.Url}"); - Console.WriteLine("Form fields to include:"); - - foreach (var field in response.Fields) - { - Console.WriteLine($" {field.Key}: {field.Value}"); - } - } -} -// snippet-end:[S3.dotnetv4.CreatePresignedPost] diff --git a/dotnetv4/S3/Actions/S3Actions.csproj b/dotnetv4/S3/Actions/S3Actions.csproj deleted file mode 100644 index 4143abf079b..00000000000 --- a/dotnetv4/S3/Actions/S3Actions.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - - - - diff --git a/dotnetv4/S3/Actions/Usings.cs b/dotnetv4/S3/Actions/Usings.cs deleted file mode 100644 index 7ac09f08058..00000000000 --- a/dotnetv4/S3/Actions/Usings.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -global using System; -global using System.Collections.Generic; -global using System.IO; -global using System.Net; -global using System.Net.Http; -global using System.Net.Http.Headers; -global using System.Text; -global using System.Threading.Tasks; -global using Amazon.S3; -global using Amazon.S3.Model; -global using Amazon.S3.Util; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.DependencyInjection; diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj index 24963eefd86..4143abf079b 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj @@ -1,4 +1,4 @@ - + Exe @@ -14,8 +14,4 @@ - - - - diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs index 2098cc14ecf..e1439c92c2a 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -76,7 +76,7 @@ public static async Task Main(string[] args) // Step 3: Display URL and fields _uiMethods.DisplayTitle("Step 3: Presigned POST URL details"); - CreatePresignedPost.DisplayPresignedPostFields(response); + DisplayPresignedPostFields(response); _uiMethods.PressEnter(_isInteractive); // Step 4: Upload file @@ -117,8 +117,20 @@ private static async Task CreateBucketAsync() { _uiMethods.DisplayTitle("Step 1: Create an S3 bucket"); - // Create a unique bucket name for the scenario - _bucketName = $"presigned-post-demo-{DateTime.Now:yyyyMMddHHmmss}".ToLower(); + // Generate a default bucket name for the scenario + var defaultBucketName = $"presigned-post-demo-{DateTime.Now:yyyyMMddHHmmss}".ToLower(); + + // Prompt user for bucket name or use default in non-interactive mode + _bucketName = _uiMethods.GetUserInput( + $"Enter S3 bucket name (or press Enter for '{defaultBucketName}'): ", + defaultBucketName, + _isInteractive); + + // Basic validation to ensure bucket name is not empty + if (string.IsNullOrWhiteSpace(_bucketName)) + { + _bucketName = defaultBucketName; + } Console.WriteLine($"Creating bucket: {_bucketName}"); @@ -127,53 +139,6 @@ private static async Task CreateBucketAsync() Console.WriteLine($"Successfully created bucket: {_bucketName}"); } - /// - /// Create a presigned POST URL with conditions. - /// - /// The Amazon S3 client. - /// The name of the bucket. - /// The object key (path) where the uploaded file will be stored. - /// When the presigned URL expires. - /// Dictionary of fields to add to the form. - /// List of conditions to apply. - /// A CreatePresignedPostResponse object with URL and form fields. - // snippet-start:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] - private static async Task CreatePresignedPostAsync( - IAmazonS3 s3Client, - string bucketName, - string objectKey, - DateTime expires, - Dictionary? fields = null, - List? conditions = null) - { - var request = new CreatePresignedPostRequest - { - BucketName = bucketName, - Key = objectKey, - Expires = expires - }; - - // Add custom fields if provided - if (fields != null) - { - foreach (var field in fields) - { - request.Fields.Add(field.Key, field.Value); - } - } - - // Add conditions if provided - if (conditions != null) - { - foreach (var condition in conditions) - { - request.Conditions.Add(condition); - } - } - - return await s3Client.CreatePresignedPostAsync(request); - } - // snippet-end:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] /// /// Create a presigned POST URL. @@ -189,7 +154,7 @@ private static async Task CreatePresignedPostAsync( // Get S3 client from S3Wrapper var s3Client = _s3Wrapper.GetS3Client(); - var response = await CreatePresignedPostAsync( + var response = await _s3Wrapper.CreatePresignedPostAsync( s3Client, _bucketName!, _objectKey, expiration); Console.WriteLine("Successfully created presigned POST URL"); @@ -300,6 +265,17 @@ private static async Task VerifyFileExistsAsync() } } + private static void DisplayPresignedPostFields(CreatePresignedPostResponse response) + { + Console.WriteLine($"Presigned POST URL: {response.Url}"); + Console.WriteLine("Form fields to include:"); + + foreach (var field in response.Fields) + { + Console.WriteLine($" {field.Key}: {field.Value}"); + } + } + /// /// Clean up resources created by the scenario. /// @@ -322,5 +298,6 @@ private static async Task CleanupAsync() } } + } // snippet-end:[S3.dotnetv4.CreatePresignedPostBasics] diff --git a/dotnetv4/S3/Actions/S3Wrapper.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs similarity index 68% rename from dotnetv4/S3/Actions/S3Wrapper.cs rename to dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs index 88db04147b8..4a29f34b8f5 100644 --- a/dotnetv4/S3/Actions/S3Wrapper.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -namespace S3Actions; +namespace S3Scenarios; /// /// Wrapper methods for common Amazon Simple Storage Service (Amazon S3) @@ -31,6 +31,54 @@ public IAmazonS3 GetS3Client() { return _s3Client; } + + // snippet-start:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] + /// + /// Create a presigned POST URL with conditions. + /// + /// The Amazon S3 client. + /// The name of the bucket. + /// The object key (path) where the uploaded file will be stored. + /// When the presigned URL expires. + /// Dictionary of fields to add to the form. + /// List of conditions to apply. + /// A CreatePresignedPostResponse object with URL and form fields. + public async Task CreatePresignedPostAsync( + IAmazonS3 s3Client, + string bucketName, + string objectKey, + DateTime expires, + Dictionary? fields = null, + List? conditions = null) + { + var request = new CreatePresignedPostRequest + { + BucketName = bucketName, + Key = objectKey, + Expires = expires + }; + + // Add custom fields if provided + if (fields != null) + { + foreach (var field in fields) + { + request.Fields.Add(field.Key, field.Value); + } + } + + // Add conditions if provided + if (conditions != null) + { + foreach (var condition in conditions) + { + request.Conditions.Add(condition); + } + } + + return await s3Client.CreatePresignedPostAsync(request); + } + // snippet-end:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] /// /// Create a bucket and wait until it's ready to use. @@ -40,31 +88,31 @@ public IAmazonS3 GetS3Client() public async Task CreateBucketAsync(string bucketName) { _logger.LogInformation("Creating bucket {bucket}", bucketName); - + var request = new PutBucketRequest { BucketName = bucketName }; var response = await _s3Client.PutBucketAsync(request); - - _logger.LogInformation("Created bucket {bucket} with status {status}", + + _logger.LogInformation("Created bucket {bucket} with status {status}", bucketName, response.HttpStatusCode); // Wait for the bucket to be available var exist = await Amazon.S3.Util.AmazonS3Util.DoesS3BucketExistV2Async(_s3Client, bucketName); - + if (!exist) { _logger.LogInformation("Waiting for bucket {bucket} to be ready", bucketName); - + while (!exist) { await Task.Delay(2000); exist = await Amazon.S3.Util.AmazonS3Util.DoesS3BucketExistV2Async(_s3Client, bucketName); } } - + return bucketName; } diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs index ddee406254a..9f358dbfa57 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs @@ -47,4 +47,26 @@ public void DisplayTitle(string strTitle) Console.WriteLine(strTitle); Console.WriteLine(SepBar); } + + /// + /// Get user input with an optional default value. + /// + /// The prompt to display to the user. + /// The default value to use if user doesn't provide input. + /// Whether to wait for user input or use default. + /// The user input or default value. + public string GetUserInput(string prompt, string? defaultValue = null, bool isInteractive = true) + { + Console.Write(prompt); + if (isInteractive) + { + var input = Console.ReadLine(); + return string.IsNullOrWhiteSpace(input) ? defaultValue ?? "" : input.Trim(); + } + else + { + Console.WriteLine(defaultValue ?? ""); + return defaultValue ?? ""; + } + } } diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs index 0e36810b5cb..830ba1dbb9c 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs @@ -1,16 +1,11 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -global using System; -global using System.Collections.Generic; -global using System.IO; global using System.Net; -global using System.Net.Http; global using System.Net.Http.Headers; -global using System.Threading.Tasks; global using Amazon.S3; global using Amazon.S3.Model; global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.DependencyInjection; -global using S3Actions; +global using Amazon.S3.Util; diff --git a/dotnetv4/S3/Tests/S3Tests.csproj b/dotnetv4/S3/Tests/S3Tests.csproj index 86b14e35ec4..81cfde53213 100644 --- a/dotnetv4/S3/Tests/S3Tests.csproj +++ b/dotnetv4/S3/Tests/S3Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -25,7 +25,6 @@ - diff --git a/dotnetv4/S3/Tests/Usings.cs b/dotnetv4/S3/Tests/Usings.cs index 8197e56ff5b..3e5abe5d22e 100644 --- a/dotnetv4/S3/Tests/Usings.cs +++ b/dotnetv4/S3/Tests/Usings.cs @@ -4,6 +4,5 @@ global using Amazon.S3; global using Microsoft.Extensions.Logging; global using Moq; -global using S3Scenarios; -global using S3Actions; global using Xunit; +global using S3Scenarios; From 6cc06d5f4bf40b57e25e8d1369b67b1c76eaf688 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 12:41:25 -0400 Subject: [PATCH 14/21] add httpfactory --- .../S3/Scenarios/S3_CreatePresignedPost/Basics.csproj | 1 + .../CreatePresignedPostBasics.cs | 11 ++++------- .../S3/Scenarios/S3_CreatePresignedPost/Usings.cs | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj index 4143abf079b..75694d45011 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Basics.csproj @@ -12,6 +12,7 @@ + diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs index e1439c92c2a..50b5f8a12af 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -1,8 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -using Microsoft.Extensions.Hosting; - namespace S3Scenarios; // snippet-start:[S3.dotnetv4.CreatePresignedPostBasics] @@ -18,6 +16,7 @@ public class CreatePresignedPostBasics public static ILogger _logger = null!; public static S3Wrapper _s3Wrapper = null!; public static UiMethods _uiMethods = null!; + public static IHttpClientFactory _httpClientFactory = null!; public static bool _isInteractive = true; public static string? _bucketName; public static string? _objectKey; @@ -34,8 +33,8 @@ public static void SetUpServices(IHost host) }); _logger = new Logger(loggerFactory); - // Now the client is available for injection. _s3Wrapper = host.Services.GetRequiredService(); + _httpClientFactory = host.Services.GetRequiredService(); _uiMethods = new UiMethods(); } @@ -46,7 +45,6 @@ public static void SetUpServices(IHost host) /// A Task object. public static async Task Main(string[] args) { - // Check for non-interactive mode _isInteractive = !args.Contains("--non-interactive"); // Set up dependency injection for Amazon S3 @@ -54,6 +52,7 @@ public static async Task Main(string[] args) .ConfigureServices((_, services) => services.AddAWSService() .AddTransient() + .AddHttpClient() ) .Build(); @@ -151,7 +150,6 @@ private static async Task CreatePresignedPostAsync( Console.WriteLine($"Creating presigned POST URL for {_bucketName}/{_objectKey}"); Console.WriteLine($"Expiration: {expiration} UTC"); - // Get S3 client from S3Wrapper var s3Client = _s3Wrapper.GetS3Client(); var response = await _s3Wrapper.CreatePresignedPostAsync( @@ -207,8 +205,7 @@ private static async Task UploadFileAsync(CreatePresignedPostResponse response) { _logger.LogInformation("Uploading file {filePath} using presigned POST URL", filePath); - // Create HttpClient to send the request - using var httpClient = new HttpClient(); + using var httpClient = _httpClientFactory.CreateClient(); using var formContent = new MultipartFormDataContent(); // Add all the fields from the presigned POST response diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs index 830ba1dbb9c..e036a122ee7 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs @@ -8,4 +8,6 @@ global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.DependencyInjection; global using Amazon.S3.Util; +global using Microsoft.Extensions.Hosting; + From 21a8d2dda62a866811767548cf0206c37b83ad8b Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 12:41:47 -0400 Subject: [PATCH 15/21] fix lint --- .doc_gen/metadata/s3_metadata.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.doc_gen/metadata/s3_metadata.yaml b/.doc_gen/metadata/s3_metadata.yaml index fee6960bc07..b3e0532a61e 100644 --- a/.doc_gen/metadata/s3_metadata.yaml +++ b/.doc_gen/metadata/s3_metadata.yaml @@ -3719,7 +3719,7 @@ s3_CreatePresignedPost: - sdk_version: 4 github: dotnetv4/S3 excerpts: - - description: Create a presigned POST URL + - description: Create a presigned POST URL. genai: most snippet_tags: - S3.dotnetv4.Scenario_CreatePresignedPostAsync From 0d6e34b8d904040cb10c02da31b6e75347fc66d9 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 12:46:54 -0400 Subject: [PATCH 16/21] update readme --- dotnetv4/S3/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dotnetv4/S3/README.md b/dotnetv4/S3/README.md index 61e43fef288..cf1e626503e 100644 --- a/dotnetv4/S3/README.md +++ b/dotnetv4/S3/README.md @@ -33,14 +33,14 @@ For prerequisites, see the [README](../README.md#Prerequisites) in the `dotnetv4 Code excerpts that show you how to call individual service functions. -- [CreatePresignedPost](Actions/S3Wrapper.cs#L71) +- [CreatePresignedPost](Scenarios/S3_CreatePresignedPost/S3Wrapper.cs#L35) ### Scenarios Code examples that show you how to accomplish a specific task by calling multiple functions within the same service. -- [](Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs) +- [Create a presigned URL](Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs) @@ -56,17 +56,17 @@ functions within the same service. -#### +#### Create a presigned URL -This example shows you how to do the following: +This example shows you how to create a presigned URL for Amazon S3 and upload an object. - - + + - - + + ### Tests From 65cc86f9ae8a72ead44a5a49b967bb56eed23b25 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 13:03:05 -0400 Subject: [PATCH 17/21] remove extra whitespace --- .../S3_CreatePresignedPost/CreatePresignedPostBasics.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs index 50b5f8a12af..57062d9102d 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -278,7 +278,6 @@ private static void DisplayPresignedPostFields(CreatePresignedPostResponse respo /// private static async Task CleanupAsync() { - if (!string.IsNullOrEmpty(_bucketName)) { Console.WriteLine($"Deleting bucket {_bucketName} and its contents..."); @@ -294,7 +293,5 @@ private static async Task CleanupAsync() } } } - - } // snippet-end:[S3.dotnetv4.CreatePresignedPostBasics] From 4c230d33f8a91542b95c6df56943fb081c60c82f Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 15:45:08 -0400 Subject: [PATCH 18/21] dotnet format --- .../CreatePresignedPostBasics.cs | 78 +++++++++---------- .../S3_CreatePresignedPost/S3Wrapper.cs | 4 +- .../S3_CreatePresignedPost/UiMethods.cs | 2 +- .../S3_CreatePresignedPost/Usings.cs | 6 +- .../Tests/CreatePresignedPostBasicsTests.cs | 8 +- dotnetv4/S3/Tests/Usings.cs | 2 +- 6 files changed, 49 insertions(+), 51 deletions(-) diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs index 57062d9102d..9cab5f092e0 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -46,7 +46,7 @@ public static void SetUpServices(IHost host) public static async Task Main(string[] args) { _isInteractive = !args.Contains("--non-interactive"); - + // Set up dependency injection for Amazon S3 using var host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) .ConfigureServices((_, services) => @@ -57,40 +57,40 @@ public static async Task Main(string[] args) .Build(); SetUpServices(host); - + try { // Display overview _uiMethods.DisplayOverview(); _uiMethods.PressEnter(_isInteractive); - + // Step 1: Create bucket await CreateBucketAsync(); _uiMethods.PressEnter(_isInteractive); - + // Step 2: Create presigned URL _uiMethods.DisplayTitle("Step 2: Create presigned POST URL"); var response = await CreatePresignedPostAsync(); _uiMethods.PressEnter(_isInteractive); - + // Step 3: Display URL and fields _uiMethods.DisplayTitle("Step 3: Presigned POST URL details"); DisplayPresignedPostFields(response); _uiMethods.PressEnter(_isInteractive); - + // Step 4: Upload file _uiMethods.DisplayTitle("Step 4: Upload test file using presigned POST URL"); await UploadFileAsync(response); _uiMethods.PressEnter(_isInteractive); - + // Step 5: Verify file exists await VerifyFileExistsAsync(); _uiMethods.PressEnter(_isInteractive); - + // Step 6: Cleanup _uiMethods.DisplayTitle("Step 6: Clean up resources"); await CleanupAsync(); - + _uiMethods.DisplayTitle("S3 Presigned POST Scenario completed successfully!"); _uiMethods.PressEnter(_isInteractive); } @@ -98,7 +98,7 @@ public static async Task Main(string[] args) { _logger.LogError(ex, "Error in scenario"); Console.WriteLine($"Error: {ex.Message}"); - + // Attempt cleanup if there was an error if (!string.IsNullOrEmpty(_bucketName)) { @@ -115,26 +115,26 @@ public static async Task Main(string[] args) private static async Task CreateBucketAsync() { _uiMethods.DisplayTitle("Step 1: Create an S3 bucket"); - + // Generate a default bucket name for the scenario var defaultBucketName = $"presigned-post-demo-{DateTime.Now:yyyyMMddHHmmss}".ToLower(); - + // Prompt user for bucket name or use default in non-interactive mode _bucketName = _uiMethods.GetUserInput( - $"Enter S3 bucket name (or press Enter for '{defaultBucketName}'): ", - defaultBucketName, + $"Enter S3 bucket name (or press Enter for '{defaultBucketName}'): ", + defaultBucketName, _isInteractive); - + // Basic validation to ensure bucket name is not empty if (string.IsNullOrWhiteSpace(_bucketName)) { _bucketName = defaultBucketName; } - + Console.WriteLine($"Creating bucket: {_bucketName}"); - + await _s3Wrapper.CreateBucketAsync(_bucketName); - + Console.WriteLine($"Successfully created bucket: {_bucketName}"); } @@ -146,15 +146,15 @@ private static async Task CreatePresignedPostAsync( { _objectKey = "example-upload.txt"; var expiration = DateTime.UtcNow.AddMinutes(10); // Short expiration for the demo - + Console.WriteLine($"Creating presigned POST URL for {_bucketName}/{_objectKey}"); Console.WriteLine($"Expiration: {expiration} UTC"); - + var s3Client = _s3Wrapper.GetS3Client(); - + var response = await _s3Wrapper.CreatePresignedPostAsync( s3Client, _bucketName!, _objectKey, expiration); - + Console.WriteLine("Successfully created presigned POST URL"); return response; } @@ -164,18 +164,18 @@ private static async Task CreatePresignedPostAsync( /// private static async Task UploadFileAsync(CreatePresignedPostResponse response) { - + // Create a temporary test file to upload string testFilePath = Path.GetTempFileName(); string testContent = "This is a test file for the S3 presigned POST scenario."; - + await File.WriteAllTextAsync(testFilePath, testContent); Console.WriteLine($"Created test file at: {testFilePath}"); - + // Upload the file using the presigned POST URL Console.WriteLine("\nUploading file using the presigned POST URL..."); var uploadResult = await UploadFileWithPresignedPostAsync(response, testFilePath); - + // Display the upload result if (uploadResult.Success) { @@ -188,7 +188,7 @@ private static async Task UploadFileAsync(CreatePresignedPostResponse response) Console.WriteLine($"Error: {uploadResult.Response}"); throw new Exception("File upload failed"); } - + // Clean up the temporary file File.Delete(testFilePath); Console.WriteLine("Temporary file deleted"); @@ -198,36 +198,36 @@ private static async Task UploadFileAsync(CreatePresignedPostResponse response) /// Helper method to upload a file using a presigned POST URL. /// private static async Task<(bool Success, HttpStatusCode StatusCode, string Response)> UploadFileWithPresignedPostAsync( - CreatePresignedPostResponse response, + CreatePresignedPostResponse response, string filePath) { try { _logger.LogInformation("Uploading file {filePath} using presigned POST URL", filePath); - + using var httpClient = _httpClientFactory.CreateClient(); using var formContent = new MultipartFormDataContent(); - + // Add all the fields from the presigned POST response foreach (var field in response.Fields) { formContent.Add(new StringContent(field.Value), field.Key); } - + // Add the file content var fileStream = File.OpenRead(filePath); var fileName = Path.GetFileName(filePath); var fileContent = new StreamContent(fileStream); fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); formContent.Add(fileContent, "file", fileName); - + // Send the POST request var httpResponse = await httpClient.PostAsync(response.Url, formContent); var responseContent = await httpResponse.Content.ReadAsStringAsync(); - + // Log and return the result _logger.LogInformation("Upload completed with status code {statusCode}", httpResponse.StatusCode); - + return (httpResponse.IsSuccessStatusCode, httpResponse.StatusCode, responseContent); } catch (Exception ex) @@ -243,13 +243,13 @@ private static async Task UploadFileAsync(CreatePresignedPostResponse response) private static async Task VerifyFileExistsAsync() { _uiMethods.DisplayTitle("Step 5: Verify uploaded file exists"); - + Console.WriteLine($"Checking if file exists at {_bucketName}/{_objectKey}..."); - + try { var metadata = await _s3Wrapper.GetObjectMetadataAsync(_bucketName!, _objectKey!); - + Console.WriteLine($"File verification successful! File exists in the bucket."); Console.WriteLine($"File size: {metadata.ContentLength} bytes"); Console.WriteLine($"File type: {metadata.Headers.ContentType}"); @@ -282,7 +282,7 @@ private static async Task CleanupAsync() { Console.WriteLine($"Deleting bucket {_bucketName} and its contents..."); bool result = await _s3Wrapper.DeleteBucketAsync(_bucketName); - + if (result) { Console.WriteLine("Bucket deleted successfully"); @@ -294,4 +294,4 @@ private static async Task CleanupAsync() } } } -// snippet-end:[S3.dotnetv4.CreatePresignedPostBasics] +// snippet-end:[S3.dotnetv4.CreatePresignedPostBasics] \ No newline at end of file diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs index 4a29f34b8f5..12e629ed827 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/S3Wrapper.cs @@ -31,7 +31,7 @@ public IAmazonS3 GetS3Client() { return _s3Client; } - + // snippet-start:[S3.dotnetv4.Scenario_CreatePresignedPostAsync] /// /// Create a presigned POST URL with conditions. @@ -171,4 +171,4 @@ public async Task GetObjectMetadataAsync( return await _s3Client.GetObjectMetadataAsync(request); } -} +} \ No newline at end of file diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs index 9f358dbfa57..fdedb6b30d5 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/UiMethods.cs @@ -69,4 +69,4 @@ public string GetUserInput(string prompt, string? defaultValue = null, bool isIn return defaultValue ?? ""; } } -} +} \ No newline at end of file diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs index e036a122ee7..f3e8a913cc9 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/Usings.cs @@ -5,9 +5,7 @@ global using System.Net.Http.Headers; global using Amazon.S3; global using Amazon.S3.Model; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.DependencyInjection; global using Amazon.S3.Util; +global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; - - +global using Microsoft.Extensions.Logging; \ No newline at end of file diff --git a/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs b/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs index 17a61f68911..2c44f6ff5d7 100644 --- a/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs +++ b/dotnetv4/S3/Tests/CreatePresignedPostBasicsTests.cs @@ -24,16 +24,16 @@ public async Task TestScenario() var loggerScenarioMock = new Mock>(); var loggerWrapperMock = new Mock>(); var uiMethods = new S3Scenarios.UiMethods(); - + _client = new AmazonS3Client(); _s3Wrapper = new S3Wrapper(_client, loggerWrapperMock.Object); - + // Set up the static fields directly S3Scenarios.CreatePresignedPostBasics._logger = loggerScenarioMock.Object; S3Scenarios.CreatePresignedPostBasics._s3Wrapper = _s3Wrapper; S3Scenarios.CreatePresignedPostBasics._uiMethods = uiMethods; S3Scenarios.CreatePresignedPostBasics._isInteractive = false; - + // Set up verification for error logging. loggerScenarioMock.Setup(logger => logger.Log( It.Is(logLevel => logLevel == LogLevel.Error), @@ -74,4 +74,4 @@ public async Task TestScenario() It.IsAny>()), Times.Never); } -} +} \ No newline at end of file diff --git a/dotnetv4/S3/Tests/Usings.cs b/dotnetv4/S3/Tests/Usings.cs index 3e5abe5d22e..7fa523be4c4 100644 --- a/dotnetv4/S3/Tests/Usings.cs +++ b/dotnetv4/S3/Tests/Usings.cs @@ -4,5 +4,5 @@ global using Amazon.S3; global using Microsoft.Extensions.Logging; global using Moq; -global using Xunit; global using S3Scenarios; +global using Xunit; \ No newline at end of file From 2e576cc93539fee767defc9fbb1f9d9c4b345ec3 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 15:49:36 -0400 Subject: [PATCH 19/21] dotnet format --- dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs b/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs index 1ee5a0bf9ea..e29cb38778f 100644 --- a/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs +++ b/dotnetv4/Bedrock-runtime/Actions/HelloBedrockRuntime.cs @@ -28,7 +28,8 @@ private static async Task Invoke(string modelId, string prompt) default: Console.WriteLine($"Unknown model ID: {modelId}. Valid model IDs are: {CLAUDE}."); break; - }; + } + ; } } } \ No newline at end of file From 632f1621860eb3d0ad5991007e6302b45ea68248 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 16:15:52 -0400 Subject: [PATCH 20/21] remove console log for response body --- .../S3_CreatePresignedPost/CreatePresignedPostBasics.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs index 9cab5f092e0..5d8d70ead1e 100644 --- a/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs +++ b/dotnetv4/S3/Scenarios/S3_CreatePresignedPost/CreatePresignedPostBasics.cs @@ -180,7 +180,6 @@ private static async Task UploadFileAsync(CreatePresignedPostResponse response) if (uploadResult.Success) { Console.WriteLine($"Upload successful! Status code: {uploadResult.StatusCode}"); - Console.WriteLine($"Response: {uploadResult.Response}"); } else { From cd2fdad6770370a22557ab355a8ffb77e52c830e Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Wed, 30 Jul 2025 17:09:19 -0400 Subject: [PATCH 21/21] fix solution --- dotnetv4/S3/S3Examples.sln | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dotnetv4/S3/S3Examples.sln b/dotnetv4/S3/S3Examples.sln index 8bba441e593..10c6bc5d832 100644 --- a/dotnetv4/S3/S3Examples.sln +++ b/dotnetv4/S3/S3Examples.sln @@ -2,10 +2,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.33414.496 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actions", "Actions", "{863BD09D-4F48-4BFF-B5E7-941503A41F0A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Actions", "Actions\S3Actions.csproj", "{F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scenarios", "Scenarios", "{C13DDD1A-438D-4E52-90FB-A496A54516C7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C51625C8-3B42-4810-BF1B-0E3C6C716FA6}" @@ -20,10 +16,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85}.Release|Any CPU.Build.0 = Release|Any CPU {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Debug|Any CPU.Build.0 = Debug|Any CPU {22C217CC-E2D9-9F79-EE15-24F9D262655E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -37,7 +29,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {F4A82877-7CC6-4DB8-A4C0-AC4E008DDD85} = {863BD09D-4F48-4BFF-B5E7-941503A41F0A} {22C217CC-E2D9-9F79-EE15-24F9D262655E} = {C13DDD1A-438D-4E52-90FB-A496A54516C7} {FB41CEEB-5F32-3E4A-F9C6-1FACCBDAFF3C} = {C51625C8-3B42-4810-BF1B-0E3C6C716FA6} EndGlobalSection