Introduction
In this article, we'll use the AWS CDK to create and deploy a service to handle notifications, with data built up from values submitted by a contact form on a simple .NET website. We'll also make use of the APIs available on the AWS SDK to communicate with our cloud resources.
Assumptions
- An AWS account with sufficient privileges to create resources
- The AWS CLI is already installed and configured with your credentials
- Familiarity with .NET web APIs beneficial but not required
- Familiarity with AWS SNS beneficial but not required
What is the AWS CDK
The AWS Cloud Development Kit (CDK) is a collection of APIs distributed as individual packages which allow developers to define resources in a programming language they're familiar with.
For example, for a .NET developer to define the infrastructure to create a new SNS topic, all that is needed is the following line of code which creates a Topic using a familiar pattern for object instantiation:
1var topic = new Topic(this, "MyTopic");
The topic object then includes all the object properties and methods available to manage storage.
This method of building and deploying cloud infrastructure allows developers to:
- reduce the learning curve needed to deploy AWS resources as users don't need to learn how to write templates using services such as CloudFormation or Terraform
- speed up the development of cloud solutions as developers don't need to navigate around the AWS console, or memorise hundreds of CLI commands
- easily create and share cloud resources across their team
- easily bring cloud resource architecture into source control
- automate infrastructure deployments as part of their application build pipelines.
The currently supported languages are:
- C#
- TypeScript/JavaScript
- Python
- Java
- Go
Install the CDK
This article assumes that you already have an AWS account, and that you have the AWS CLI installed and configured with your credentials - if you don't, start by following the steps detailed in the AWS documentation. You'll also need to download and install Node with NPM.
Once you have those bits set up and configured, you can then install the CDK globally from a terminal using NPM:
1npm install -g aws-cdk
Initialise the CDK in your project
Once installed, navigate to your local repository directory and create a folder called "AWS", then open up a PowerShell terminal in the new directory.
1cd <your source code directory>2mkdir AWS3cd AWS
You can initialise a new CDK project using the following command:
1cdk init app --language csharp
This command generates a few starter files in your AWS folder. In the src directory of the generated files, you'll find a solution file called Aws.sln, open this up in Visual Studio.
Project structure
The main parts of your logic will be contained within the src directory, but the command also generates a few other files which you may find useful to include in your solution tree. Right click the solution to add a solution folder, then add the .gitignore, cdk.json and README.md files to this folder.
The only other files worth mentioning are the Program.cs and the AwsStack.cs files. The former is where you can define the collection of CloudFormation stacks needed for your cloud solution, along with any additional configuration required for your AWS account, and the latter is where you will define the resources needed for the stack we will shortly be creating. The stack we'll be creating will only be for handling notifications with SNS, so we'll rename this file and any associated references to NotificationStack.

Bootstrap CDK with your AWS account
Before you're able to create any resources in AWS using the CDK, you first need to "bootstrap" your account to prepare it for deployments. This process creates a CloudFormation stack called "CDKToolkit", and generates resources using the following services:
- Systems Manager (parameter)
- IAM (roles, policies)
- ECR (repository)
- S3 (bucket, bucket policy)
Bootstrapping is performed using the following command:
1cdk bootstrap
Note: You may incur charges for resources created by bootstrapping. You can easily remove the resources created during this tutorial with cdk destroy. To remove the bootstrap resources, you will need to manually delete the "CDKToolkit" stack - this can be done via the console, or the CLI with aws cloudformation delete-stack --stack-name CDKToolkit.
Create your resources
As touched on in the introduction to this article, we'll be creating the resources needed to allow our app to send an email using Simple Notification Service (SNS). The example used here is a website contact form, which is submitted using JavaScript's fetch API to a .NET web API endpoint.
In order to reference the resource classes and methods available with the AWS CDK, you'll need to bring the applicable NuGet packages into your project. This article will only make use of SNS, so add the Amazon.CDK.AWS.SNS package, which includes the core CDK library, among a few others services.

Constructs
The resource classes (constructs) built into the CDK packages come in three distinct flavours, which determine the level of detail needed to define them; Level 1, 2 and 3 (L1/L2/L3).
- L1: Prefixed with
Cfn, these classes represent objects which require the highest level of configuration. No default values are set, and the objects don't include any handy helper methods. Recommended only if you need very fine-grained control of the resource being created (example:CfnTopic) - L2: Named after the AWS resource without any prefixes, these classes are the ones more commonly used as they come configured with sensible (opinionated) default values, and include various contextual helper methods, such as
AddSubscription(example:Topic) - L3: Named after their overall function, these classes are used to create the resources necessary to satisfy a particular use case (example:
ApplicationLoadBalancedEc2Service). Further details regarding the available patterns can be found in the CDK documentation.
Create a Topic and Subscription
This stack will only make use of a single service, SNS, which we can define with two simple object instantiations - a topic and a subscription to that topic - and we'll use the L2 versions of these constructs to keep things simple.
1using Amazon.CDK;2using Amazon.CDK.AWS.SNS;3using Construct = Constructs.Construct;45namespace Notifications6{7 public class NotificationsStack : Stack8 {9 internal NotificationsStack(Construct scope, string id, IStackProps props) : base(scope, id, props)10 {11 var topic = new Topic(this, "ContactFormSubmission", new TopicProps12 {13 TopicName = "ContactFormSubmission"14 });1516 var subscription = new Subscription(this, "Subscription", new SubscriptionProps17 {18 Topic = topic,19 Region = "eu-west-2",20 Protocol = SubscriptionProtocol.EMAIL,21 Endpoint = "contact@tomjones.dev"22 });23 }24 }25}
The NotificationStack signature is unchanged from the CDK initialisation, and in this case we've added a Topic called "ContactFormSubmission", with a single Subscription specifying that the subscriber (me) will be notified by email. In a real-life scenario, you're more likely to add subscribers manually or programmatically after deployment rather than them being hardcoded into the stack like this.
Note: the EMAIL protocol doesn't support the first in first out (FiFo) topic type, and will fail to build during deployment, however this value defaults to false and therefore doesn't need to be declared in the Topic definition for in this scenario.
Deploy your resources
Deploying a CDK project generates a CloudFormation template, which is then uploaded to AWS and processed like any other CloudFormation stack. If you're familiar with CloudFormation templates, you may wish to confirm the output of the objects you've defined in code prior to deployment - this can be done by running the synthesise command:
1cdk synth
Which outputs a template similar to below for my example:
1Resources:2 ContactFormSubmissionEA03D76B:3 Type: AWS::SNS::Topic4 Properties:5 TopicName: ContactFormSubmission6 Metadata:7 aws:cdk:path: NotificationsStack/ContactFormSubmission/Resource8 Subscription391C9821:9 Type: AWS::SNS::Subscription10 Properties:11 Endpoint: contact@tomjones. dev12 Protocol: email13 Region: eu-west-214 TopicArn:15 Ref: ContactFormSubmissionEA03D76B16 Metadata:17 aws:cdk:path: NotificationsStack/Subscription/Resource18 CDKMetadata:19 Type: AWS::CDK::Metadata20 Properties:21 Analytics: v2:deflate64:H4sIAAAAAAAA/02JsQ6CMBRFv4WdPihENweDrgxAXE0pNXmifU1fKwPx303TxdzhnntuA7JtoS7UxkIvq3jhDPsYlF5LtfGdLcM+kUNddg+bYYwza48uINlk//c3icEwRa9N4o7sgvm5UOhNqI5QQ3OooL9OHXlzdk7cjGcke/q0ICsJdUrxZEThow34NjDk/gF9G3b6rgAAAA==22 Metadata:23 aws:cdk:path: NotificationsStack/CDKMetadata/Default24 Condition: CDKMetadataAvailable25Conditions:26// ...Removed for brevity
Once you've checked through the generated CloudFormation resource structures and you're ready to deploy, run the following command to push your infrastructure into AWS:
1cdk deploy
If you hardcoded a subscription email as I've done in this example, you'll see an email pop into your mailbox during deployment to confirm your subscription to the new topic. This will need to be confirmed before you're able to receive notifications.
For further details on the properties and methods available as part of the CDK package for SNS, have a look through the SNS reference documentation.
Implement your deployed resources using the AWS SDK
In this section, we'll implement our deployed SNS topic into a simple .NET application which takes a few user details and generates a notification to be sent to the subscribed email address.
Add the AWS SDK for SNS to your project by installing the AWSSDK.SimpleNotificationService NuGet package.

Add a controller endpoint which implements the SDK, this is an example method in a HomeController class:
1[HttpPost]2public async Task<IActionResult> SubmitFeedback(3 [FromForm] Feedback feedback,4 CancellationToken cancellationToken = default)5{6 var key = "your-aws-key";7 var secret = "your-aws-secret-key";8 var region = RegionEndpoint.EUWest2;910 var message = new StringBuilder();11 message.Append($"Name: {feedback.Name}\n");12 message.Append($"Email: {feedback.Email}\n");13 message.Append($"Message: {feedback.Message}\n");1415 var client = new AmazonSimpleNotificationServiceClient(key, secret, region);16 var request = new PublishRequest("arn:aws:sns:eu-west-2:xxxxxxxxxxxx:ContactFormSubmission", message.ToString(), "Contact Form Submission");1718 var response = await client.PublishAsync(request, cancellationToken);1920 return response.HttpStatusCode == HttpStatusCode.OK21 ? Ok()22 : BadRequest();23}2425public class Feedback26{27 public string Name { get; set; } = string.Empty;2829 public string Email { get; set; } = string.Empty;3031 public string Message { get; set; } = string.Empty;32}
You can then run your project and call your endpoint using an API tool like Postman.

And that's everything needed to test the integration of your new CDK deployed resources, however you may want to read on for a better method of communicating securely with AWS resources, and also if you want to add a simple UI to your API endpoint.
Managing credentials
The endpoint above hardcodes AWS credentials, but a better practise is to make use of IConfiguration and appsettings.json, or another AWS service called Secrets Manager. You can read up on the various options available to manage secure credential storage in my article Manage app credentials with AWS Secrets Manager.
Alternatively, if you're deploying your application to EC2, you can attach a role to your instance and create a policy which grants access to the Publish command of your topic, an example policy is shown below:
1{2 "Version": "2012-10-17",3 "Statement": [4 {5 "Sid": "VisualEditor0",6 "Effect": "Allow",7 "Action": "sns:Publish",8 "Resource": "arn:aws:sns:eu-west-2:xxxxxxxxxxxxx:ContactFormSubmission"9 }10 ]11}
You can then remove the key, secret and region variables from the endpoint and make use of the parameterless AmazonSimpleNotificationServiceClient constructor.
Implement a UI for our endpoint
As my use case is for a contact form on a website, I'll add a simple form element to represent the request body:
1<form method="post">2 <label>Name</label>3 <input type="text" name="name">45 <label>Email</label>6 <input type="email" name="email">78 <label>Message</label>9 <textarea name="message"></textarea>1011 <button>Submit</button>12</form>
My contact form is in a popup, so I'll include a bit of JavaScript to form a REST request to the endpoint to avoid a full page refresh when the form is submitted:
1document.addEventListener("DOMContentLoaded", () => {2 addFeedbackEventListeners();3});45function addFeedbackEventListeners(event) {6 document.querySelector('form button').addEventListener('click', submitFeedback);7}89async function submitFeedback(event) {10 event.preventDefault();1112 const form = document.querySelector('form');13 const formData = new FormData(form)1415 const response = await fetch('api/home/submitFeedback', {16 method: 'POST',17 body: formData,18 });19}
