HOWTO Build an ASP.NET CORE Web App on AWS Running Linux with DynamoDB – Part 1 | Quisitive
HOWTO Build an ASP.NET CORE Web App on AWS Running Linux with DynamoDB – Part 1
December 9, 2016
Quisitive
Read our blog.

Typical ASP.NET Web applications run on windows servers, front ended with IIS web services and SQL Server as the underlying database. Running in the cloud in Azure is also extremely popular.

For this exercise I wanted to change everything. We are going to build a web app in Visual Studio 2015, provide support for ASP.NET Core to run on Linux, add permissions to run within Amazon AWS and support MVC Style CRUD operations with a NoSQL database service at AWS called DynamoDB.

I will take you through most of the manual process in two posts. Part 1 will get Visual Studio 2015 setup and connected to DynamoDB. Part 2 will cover running the new application on an EC2 Linux Instance within the AWS cloud. Part 3 will automate deployment using AWS OpsWorks and Custom Chef Recipes. Part 4 will utilize the AWS Elastic Beanstalk service as an alternative to OpsWorks.

Step 1. Create a Free Tier Account from AWS.

You will need an account from AWS. Obtaining a Free Account and using the Free Tier usage will result in little if any charges and most likely none.

Step 2. Create Permissions to access you AWS Resources from Visual Studio and for EC2 instances.

Within the web application we will access the AWS DynamoDB service to create, read, update, and delete database entries. Also in Part 2 of this post we will pull the published web application from an S3 bucket down to the EC2 instance. This is achieved through an IAM role.

Once logged into the console click on IAM, select Roles from the side menu then Create New Role.

start1
  1. Give it a name and click Next Step.
  2. Select EC2 Role Type and attach the built in policies called “AmazonDynamoDBFullAccess” and “AmazonS3FullAccess”.
  3. Click Next Step then Create Role.

When you done select the new role you just created and it should look something like this.

start0

Step 3. Create a DynamoDB table.

  1. Click on the main menu within the AWS Console and select DynamoDB.
  2. Select Create table and name “ProductTable”. Use “Id” for the Primary Key of type string.
  3. Everything else is default so click Create.
start2

It will take a few minutes to create but that’s it for now in the Amazon Console. On to Visual Studio.

Step 4. Setting Visual Studio 2015 with AWS Support.

  1. Download and install AWS Toolkit for Visual Studio from here.
  2. Launch Visual Studio 2015 and create a new ASP.NET Core Web Application and give it a name then click “OK”.
  3. Make sure Web Application is selected then select Change Authentication and select “Individual User Accounts”. This step was required in order for Scaffolding to work without a lot of manual additions.
  4. This project will be using MicroSoft.NECore.app version 1.1.0 so it will require the following updates to build properly if you are using version MicroSoft.NECore.app 1.0.1.
  5. You will need to modify the projects global.json and project.json files to target the 1.1.0 framework. For global.json I used “1.0.0-preview2-1-003177”. This is also a helpful reference.
  6. Rebuild the project and make it complies with v1.1. The Debug output should show something like this:
    Compiling DemoProject1 for .NETCoreApp,Version=v1.1
  7. Right Click on application in Solution Explorer and select Manage NuGet Packages. Select Browse tab and search for AWSSDK and install:
    1. AWSSDK.Core
    2. AWSSDK.Extensions.NETCore.Setup
    3. AWSSDK.DynamoDBv2
  8. Clean and Rebuild Project

Step 5. Setting Up the Web Application with AWS Authentication

This activity has its own step because it’s that important to get it right. The application will use AWS SDK version 3. The docs are here.

First we will run the app in visual studio to make sure it works. It will run differently at AWS on an EC2 instance where it will get authorization and permissions from the meta-data on the OS.

Add a new JSON file to the application called “appsettings.Development.json” with the following contents. Make sure you use the Role Name you created above and the correct AWS Region.

{
  "AWS": {
  "Profile": "YourRoleName",
  "Region": "Your Region"
 }
}

Step 6. Adding Support for DynamoDB using Dependency Injection.

Read the details on Dependency Injection here.

1. Add the following to the top of Startup.cs

using Amazon.DynamoDBv2;

2. Add the following to ConfigureServices() in Startup.cs below services.AddMvc();

services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
services.AddAWSService();

Step 7. Adding a “Book” model to represent items in DynamoDB

Each item in the table “ProductCatalog” will be a JSON object containing the entries from the “Book” model below.

1. In Solutions Explorer right click Models and add a class called “Book”. Replace the contents of the file “Book.cs” with the following:


using Amazon.DynamoDBv2.DataModel;
 
namespace Global.Models
{
    [DynamoDBTable("ProductCatalog")]
    public class Book
    {
        [DynamoDBHashKey]    //Partition key
        public string Id { get; set; }
        public string Title { get; set; }
        public string ISBN { get; set; }
        public double Price { get; set; }
        public string PageCount { get; set; }
        public string ProductCategory { get; set; }
        public bool InPublication { get; set; }
 
    }
}

Step 8. Add a new controller class to handle CRUD Operations.

  1. In Solutions Explorer right click on Controllers select add and then select Controller.
  2. Choose option “MVC Controller with views, using Entity Framework
  3. Update the Add Controller Options with the following.
start8

Your Data context class will look somewhat different. Select Add.
This will build the necessary views and new controller class:

start9

The “BookController” class contains the CRUD operations to access a SQL Server database. Since we are interested in connecting to AWS DynamoDB it will need to be replaced.
Replace the contents of BookController.cs with the following:

using System.Collections.Generic;using System.Threading.Tasks;using Microsoft.AspNetCore.Mvc;using Microsoft.EntityFrameworkCore;using Global.Models;using Global.Data;using Amazon.DynamoDBv2; namespace Global.Controllers{    public class BooksController : Controller    {        private IAmazonDynamoDB dynamoDBClient;          public BooksController(IAmazonDynamoDB dynamoDBClient)        {            //_context = context;            this.dynamoDBClient = dynamoDBClient;        }         // GET: Books        public async Task Index()        {            DynamoDBServices service = new DynamoDBServices(dynamoDBClient);            List books = await service.GetBooksAsync();            return View(books);        }         // GET: Books/Details/5        public async Task Details(string id)        {            if (id == null)            {                return NotFound();            }             DynamoDBServices service = new DynamoDBServices(dynamoDBClient);            Book book = await service.GetBookAsync(id);             if (book == null)            {                return NotFound();            }             return View(book);        }         // GET: Books/Create        public IActionResult Create()        {            return View();        }         // POST: Books/Create        // To protect from overposting attacks, please enable the specific properties you want to bind to, for         // more details see http://go.microsoft.com/fwlink/?LinkId=317598.        [HttpPost]        [ValidateAntiForgeryToken]        public async Task Create([Bind("Id,ISBN,InPublication,PageCount,Price,ProductCategory,Title")] Book book)        {            if (ModelState.IsValid)            {                DynamoDBServices service = new DynamoDBServices(dynamoDBClient);                Book newBook = await service.InsertBook(book);                return RedirectToAction("Index");            }            return View(book);        }         // GET: Books/Edit/5        public async Task Edit(string id)        {            if (id == null)            {                return NotFound();            }             DynamoDBServices service = new DynamoDBServices(dynamoDBClient);            Book book = await service.GetBookAsync(id);             //var book = await _context.Book.SingleOrDefaultAsync(m => m.Id == id);            if (book == null)            {                return NotFound();            }            return View(book);        }         // POST: Books/Edit/5        // To protect from overposting attacks, please enable the specific properties you want to bind to, for         // more details see http://go.microsoft.com/fwlink/?LinkId=317598.        [HttpPost]        [ValidateAntiForgeryToken]        public async Task Edit(string id, [Bind("Id,ISBN,InPublication,PageCount,Price,ProductCategory,Title")] Book book)        {            if (id != book.Id)            {                return NotFound();            }             if (ModelState.IsValid)            {                try                {                    DynamoDBServices service = new DynamoDBServices(dynamoDBClient);                    Book newBook = await service.UpdateBookAsync(book);                }                catch (DbUpdateConcurrencyException)                {                        throw;                }                return RedirectToAction("Index");            }            return View(book);        }         // GET: Books/Delete/5        public async Task Delete(string id)        {            if (id == null)            {                return NotFound();            }             DynamoDBServices service = new DynamoDBServices(dynamoDBClient);            Book book = await service.GetBookAsync(id);            if (book == null)            {                return NotFound();            }             return View(book);        }         // POST: Books/Delete/5        [HttpPost, ActionName("Delete")]        [ValidateAntiForgeryToken]        public async Task DeleteConfirmed(string id)        {            DynamoDBServices service = new DynamoDBServices(dynamoDBClient);            await service.DeleteBookAsync(id);            return RedirectToAction("Index");        }    }
}

Create a new class with in that Data directory called DynamoDBServices.cs and replace all contents with the following:

using System.Threading.Tasks;
using System.Collections.Generic;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.DataModel;
using Global.Models;
 
namespace Global.Data
{
    public class DynamoDBServices
    {
        IAmazonDynamoDB dynamoDBClient { get; set; }
 
        public DynamoDBServices(IAmazonDynamoDB dynamoDBClient)
        {
            this.dynamoDBClient = dynamoDBClient;
        }
 
        public async Task InsertBook(Book book)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            // Add a unique id for the primary key.
            book.Id = System.Guid.NewGuid().ToString();
            await context.SaveAsync(book, default(System.Threading.CancellationToken));
            Book newBook = await context.LoadAsync(book.Id, default(System.Threading.CancellationToken));
            return book;
        }
 
        public async Task GetBookAsync(string Id)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            Book newBook = await context.LoadAsync(Id, default(System.Threading.CancellationToken));
            return newBook;
        }
 
        public async Task UpdateBookAsync(Book book)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            await context.SaveAsync(book, default(System.Threading.CancellationToken));
            Book newBook = await context.LoadAsync(book.Id, default(System.Threading.CancellationToken));
            return newBook;
        }
        public async Task DeleteBookAsync(string Id)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            await context.DeleteAsync(Id, default(System.Threading.CancellationToken));
        }
        public async Task<List> GetBooksAsync()
        {
            ScanFilter scanFilter = new ScanFilter();
            scanFilter.AddCondition("Id", ScanOperator.NotEqual, 0);
 
            ScanOperationConfig soc = new ScanOperationConfig()
            {
                // AttributesToGet = new List { "Id", "Title", "ISBN", "Price" },
                Filter = scanFilter
            };
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            AsyncSearch search = context.FromScanAsync(soc, null);
            List documentList = new List();
            do
            {
                documentList = await search.GetNextSetAsync(default(System.Threading.CancellationToken));
            } while (!search.IsDone);
 
            return documentList;
        }
    }
}

Create a new class with in that Data directory called DynamoDBServices.cs and replace all contents with the following:


using System.Threading.Tasks;
using System.Collections.Generic;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.DataModel;
using Global.Models;
 
namespace Global.Data
{
    public class DynamoDBServices
    {
        IAmazonDynamoDB dynamoDBClient { get; set; }
 
        public DynamoDBServices(IAmazonDynamoDB dynamoDBClient)
        {
            this.dynamoDBClient = dynamoDBClient;
        }
 
        public async Task InsertBook(Book book)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            // Add a unique id for the primary key.
            book.Id = System.Guid.NewGuid().ToString();
            await context.SaveAsync(book, default(System.Threading.CancellationToken));
            Book newBook = await context.LoadAsync(book.Id, default(System.Threading.CancellationToken));
            return book;
        }
 
        public async Task GetBookAsync(string Id)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            Book newBook = await context.LoadAsync(Id, default(System.Threading.CancellationToken));
            return newBook;
        }
 
        public async Task UpdateBookAsync(Book book)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            await context.SaveAsync(book, default(System.Threading.CancellationToken));
            Book newBook = await context.LoadAsync(book.Id, default(System.Threading.CancellationToken));
            return newBook;
        }
        public async Task DeleteBookAsync(string Id)
        {
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            await context.DeleteAsync(Id, default(System.Threading.CancellationToken));
        }
        public async Task<List> GetBooksAsync()
        {
            ScanFilter scanFilter = new ScanFilter();
            scanFilter.AddCondition("Id", ScanOperator.NotEqual, 0);
 
            ScanOperationConfig soc = new ScanOperationConfig()
            {
                // AttributesToGet = new List { "Id", "Title", "ISBN", "Price" },
                Filter = scanFilter
            };
            DynamoDBContext context = new DynamoDBContext(dynamoDBClient);
            AsyncSearch search = context.FromScanAsync(soc, null);
            List documentList = new List();
            do
            {
                documentList = await search.GetNextSetAsync(default(System.Threading.CancellationToken));
            } while (!search.IsDone);
 
            return documentList;
        }
    }
}

Step 9. Rebuild and Launch Application

You should see the default web app splash screen again. Now add /Books to the url:
http://localhost:port/Books

start11

Now you have complete CRUD Operations ready with Amazon DynamoDB. Test it out by creating, editing and deleting new entries.

Final Step. Publish and prepare the web app to run in the cloud.

In order to run this application in the cloud we will publish the code to another directory and zip it up. We could in fact just zip up the entire project and build the app on the EC2 instance but we will leave that for another post.

  1. In the menu click Build then Publish <YouAppName> select Custom then name it.
  2. Select Ok and choose a location to publish.
  3. Publish Method Should be File System.
  4. Accept the defaults and Publish.
  5. Last step is to compress the directory to a zip file to be uploaded in Part 2 of this post.

That’s it for now. Part 2 will cover running the new application on an EC2 Instance within the AWS cloud.