AWS Developer Tools Blog

Using Amazon CloudFront with ASP.NET Apps

Today, I’m going to talk about using Amazon CloudFront to boost the performance of ASP.NET web apps that are deployed to Amazon Web Services. CloudFront is a content delivery service that can cache content in edge locations across the world to give users low-latency access to static content and relieve some of the pressure from web servers.

The main entity that is created in CloudFront is a distribution. A CloudFront distribution contains all the configuration of how content will be cached and the domain name that users will use to access the content. You can create distributions using many different tools like the AWS Management Console or the AWS Explorer in the AWS Toolkit for Visual Studio. I’m going show how to create a distribution using AWS CloudFormation to script the creation of our CloudFront distribution so it can be easily reproduced in other web applications. Then I’ll show how to use the AWS Toolkit to deploy it.

Deploying an App

First, I’m going to deploy an application to AWS using AWS Elastic Beanstalk. To keep things simple, I’m going to create a new project in Visual Studio, select ASP.NET MVC 4 Web Application, and then select Internet Application. To keep the focus on CloudFront, I’m going to only lightly cover Elastic Beanstalk deployments. For more in-depth information on deployments, please review our Elastic Beanstalk user guide.

The first step in deploying is to right-click on our project and select Publish to AWS.

Then walk through the wizard setting using the following instructions.

Template Page

  • Select Account and Region to deploy to
  • Select Deploy new application with template
  • Select AWS Elastic Beanstalk
  • Click Next

Application Page

  • Leave values at the default and click Next

Environment Page

  • Enter a name for the environment
  • Verify the environment URL is unique
  • Click Next

AWS Options Page

  • Select a key pair or create a new one
  • Click Next

Application Options Page

  • Click Next

Amazon RDS Database Security Group Page

  • Click Next

Review Page

  • Click Deploy

After you click Deploy, the application will be built and deployed to Elastic Beanstalk. The AWS Explorer will be refreshed showing the new environment and the Environment view will be displayed as well.

Creating the AWS CloudFormation Template

CloudFormation uses templates—which are JSON text files—to script the creation of AWS resources. I’m going to create a template that will create my CloudFront distribution using the CloudFormation editor that is part of the Visual Studio Toolkit. To get started, I’m going to right-click on the solution, select Add New Project, and then select the AWS CloudFormation project.

In the project wizard, I’m going to select Create with empty template and then click Finish.

Once the project is created, I can use the following template to create the distribution. In the CloudFormation editor, you can hover over any of the keys to get a description of what they mean.

{
    "AWSTemplateFormatVersion" : "2010-09-09",

    "Description" : "",

The only parameter needed is the domain name of our application. In this case, it will be the URL of the Elastic Beanstalk environment. In other examples, this could be the DNS name of an Elastic Load Balancer or EC2 instance.

    "Parameters" : {
        "CloudFrontDomain" : {
            "Type" : "String",
            "Description" : "The domain of the website"
        }
    },

    "Resources" : {

Define the CloudFront distribution.

"Distribution" : {
            "Type" : "AWS::CloudFront::Distribution",
            "Properties" : {
                "DistributionConfig" : {
                    "DefaultRootObject" : "/",

An origin is the source of content for a distribution. In this case, there is only one origin, which is the Elastic Beanstalk environment. In advanced situations, there could be multiple origins. One use case for having multiple origins would be having an Elastic Beanstalk environment to serve up the dynamic content and the static content coming from an Amazon S3 bucket. For this advanced case, refer to the CloudFront documentation on setting up multiple cache behaviors.

"Origins" : [
                        {
                            "DomainName" : { "Ref" : "CloudFrontDomain" },
                            "Id" : "webapp-dns",
                            "CustomOriginConfig" : {
                                "HTTPPort" : "80",
                                "HTTPSPort" : "443",
                                "OriginProtocolPolicy" : "match-viewer"
                            }
                        }
                    ],

All distributions have a default cache behavior that tells which origin to use. The query string needs to be forwarded since the application is serving up dynamic content based on the query string.

"DefaultCacheBehavior" : {
                        "ForwardedValues" : {
                            "QueryString" : true
                        },
                        "TargetOriginId"  : "webapp-dns",
                        "ViewerProtocolPolicy" : "allow-all"
                    },
                    "Enabled" : true,

This section enables CloudFront access logging. The logs are similar to IIS logs and are great for understanding the request coming into your site.

"Logging" : {
                        "Bucket" : {"Fn::GetAtt" : [ "LoggingBucket", "DomainName"]},
                        "Prefix" : "cflogs/"
                    }
                }
            }
        },

Create an Amazon S3 bucket for the CloudFront logs to be delivered to.

"LoggingBucket" : {
            "Type" : "AWS::S3::Bucket",
            "Properties" : {
            }
        }
    },

    "Outputs" : {

Output the URL to access our web application through CloudFront.

"CloudFrontDomainName" : {
            "Value" : {"Fn::Join" : [ "", ["http://", {"Fn::GetAtt" : [ "Distribution", "DomainName"]}, "/" ] ]},
            "Description" : "Use this URL to access your website through CloudFront"
        },

Output the name of the Amazon S3 bucket created for the CloudFront logs to be delivered to.

"LoggingBucket" : {
            "Value" : { "Ref" : "LoggingBucket" },
            "Description" : "Bucket where CloudFront logs will be written to"
        }
    }
}

Deploying the AWS CloudFormation Template

With the template done, the next step is to deploy to CloudFormation, which will create a stack that represents all the actual AWS resources defined in the template. To deploy this template, I right-click on the template in Solution Explorer and then click Deploy to AWS CloudFormation.

On the first page of the wizard, I’ll enter cloudfrontdemo for the name of the stack that is going to be created. Then I click Next.

On the second page, which is for filling out any parameters defined in the template, I enter the Elastic Beanstalk environment DNS name, then click Next for the review page, and then click Finish.

Now CloudFormation is creating my stack with my distribution. Once the status of the stack transitions to CREATE_COMPLETE, I can check the Outputs tab to get the URL of the CloudFront distribution.

Summary

When users hit my application using the distribution’s URL, they are directed to the nearest edge location to them. The edge location either returns its cached value for the request, and if it doesn’t contain a value, it will reach back to my Elastic Beanstalk environment to fetch the latest value. How long CloudFront caches values is controlled by Cache-Control headers returned by the origin, which in this case is my Elastic Beanstalk environment. If there is no Cache-Control header, then CloudFront defaults to 24 hours, which will be the case for all my static content such as images and javascript. By default, ASP.NET is going to return the Cache-Control header with a value of private for dynamic content, which indicates that all or part of the response message is intended for a single user and must not be cached by a shared cache. This way the content coming from my ViewControllers will always be fresh, whereas my static content can be cached. If the dynamic content can be cached for periods of time, then by using the HttpResponse object, I can indicate that to CloudFront. For example, the code snippet below will let CloudFront know that this content can be cached for 30 minutes.

protected void Page_Load(object sender, EventArgs e)
{
    Response.Cache.SetCacheability(HttpCacheability.Public);
    Response.Cache.SetMaxAge(TimeSpan.FromMinutes(30));
}

For more information on controlling the length of caches, review the CloudFront documentation on expiration.