Skip to content

DEV331 Option 3: Refactor a traditional ASPX forms application to a fully serverless model.

Duration: ~45 minutes
Credentials:
Windows local user: .\developer
Windows local password: ILove.Net!
SQL user: developer
SQL pass: ILove.Net!
Development Machine Region: Canada Central
Note – All added files are also in the C:\source\refactor folder on your development machine

Step 1: Close any currently opened Visual Studio solutions, Create a new project, select AWS Lambda – AWS Serverless Application (.NET Core), call the solution reInventRefactor

a Select ASP.NET Core Web App on the next screen: a

Step 2: Now go to Tools, NuGet Package Manager, Manage NuGet Packages for Solution, make sure you are on the Browse tab and install the following packages into the solution:

AWSSDK.DynamoDBv2
AWSSDK.Extensions.NETCore.Setup
Swashbuckle.AspNetCore

(accept all the agreements when they come up)
When you are finished it should look like this: a

Step 3: Next open the Startup.cs file, add the following using statements:

using Swashbuckle.AspNetCore.Swagger;
using Amazon.DynamoDBv2;

Modify the ConfigureServices method to add the following lines at tbe end of the method:

services.AddAWSService<IAmazonDynamoDB>();
services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "reInvent API", Version = "v1" });
    });

Modify the Configure method to add the following lines after app.UseMvc():

    // Enable middleware to serve generated Swagger as a JSON endpoint.
    app.UseSwagger();
    // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 
    // specifying the Swagger JSON endpoint.
    //access swagger through /swagger/
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "reInvent API V1");
    });

Step 4: Add the Data Layer


Add a new Folder to the project called DataLayer
In the DataLayer folder add a new class called ContactsDB.cs – This will have all of the database logic for DynamoDB.

Here is what your solution explorer should look like: a
Here is the contents of the ContactsDB.cs file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;

namespace reInventRefactor.DataLayer
{
    public class ContactsDB
    {
        public async Task<IEnumerable<Models.Contact>> GetAllContacts(IAmazonDynamoDB ddbClient)
        {
            try
            {
                var context = new DynamoDBContext(ddbClient);
                var query = context.ScanAsync<Models.Contact>(new List<ScanCondition>());
                return await query.GetRemainingAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error fetching all contacts: {ex.Message}");
            }
            return null;
        }

        public async Task<Models.Contact> GetContactByID(IAmazonDynamoDB ddbClient, int id)
        {
            Models.Contact contact = null;
            try
            {
                var context = new DynamoDBContext(ddbClient);
                contact = await context.LoadAsync<Models.Contact>(id);
                return contact;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error looking for contact {id}: {ex.Message}");
            }
            return contact;
        }

        public async Task<int> SaveContact(IAmazonDynamoDB ddbClient, Models.Contact contact)
        {
            if (contact == null)
            {
                return -1;
            }
            if (contact.ID<1)
            {
                contact.ID = new Random().Next(100,int.MaxValue);
            }
            try
            {
                var context = new DynamoDBContext(ddbClient);
                await context.SaveAsync(contact);
                return contact.ID;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error saving contact {contact.ID}: {ex.Message}");
            }
            return -1;
        }

        public async Task DeleteContactByID(IAmazonDynamoDB ddbClient, int id)
        {
            try
            {
                var context = new DynamoDBContext(ddbClient);
                await context.DeleteAsync<Models.Contact>(id);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Could not delete contact {id}: {ex.Message}");
            }
        }
   }
}

Step 5: Add the Contact Model

Now add a Folder to the project called Models and in that folder create a new class called Contact.cs, this is the contents of that file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DataModel;

namespace reInventRefactor.Models
{
    [DynamoDBTable("Contacts")]
    public class Contact
    {
        public Contact() { }

        [DynamoDBHashKey(AttributeName ="ContactID")]
        public int ID { get; set; }
        public string Name { get; set; }
        public string Mobile { get; set; }
        public string Address { get; set; }
    }
}

*At this point your project should build, build your project and make sure it builds, if it doesn’t fix it until it builds*

Step 6: Now add a folder called Controllers to the project, then right click on the Controllers folder, click Add, Class, name the controller ContactController

Step 7: Now that you have the controller, add the methods to call the data layer, you should end up with:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Amazon.DynamoDBv2;


namespace reInventRefactor.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ContactController : ControllerBase
    {
        private readonly IAmazonDynamoDB _ddbClient;

        public ContactController(IAmazonDynamoDB ddbClient)
        {
            _ddbClient = ddbClient;
        }

        // GET: api/Contact
        [HttpGet]
        public async Task<IEnumerable<Models.Contact>> Get()
        {
            var result = new List<Models.Contact>();

            return await new DataLayer.ContactsDB().GetAllContacts(_ddbClient);
        }

        // GET: api/Contact/1234b9b0-4bb6-452d-ad17-383518b50269
        [HttpGet("{id}")]
        public async Task<Models.Contact> Get(int id)
        {
            if (id <1)
            {
                return null;
            }
            return await new DataLayer.ContactsDB().GetContactByID(_ddbClient, id);
        }
        // Post: api/Contact
        [HttpPost]
        public async Task<int> Post([FromBody] Models.Contact contact)
        {
            return await new DataLayer.ContactsDB().SaveContact(_ddbClient, contact);
        }

        [HttpPut]
        public async Task<StatusCodeResult> Put([FromBody] Models.Contact contact)
        {
            if (contact == null)
            {
               return BadRequest();
            }
            contact.ID = await new DataLayer.ContactsDB().SaveContact(_ddbClient, contact);
            return Ok();
        }

       // DELETE: api/Contact/01234567-89ab-cdef-0123-45678924d8e7
        [HttpDelete("{id}")]
        public async Task<StatusCodeResult> Delete(int id)
        {
           await new DataLayer.ContactsDB().DeleteContactByID(_ddbClient, id);
           return Ok();
        }
    }
}

Step 8: Next, let’s modify the appsettings.Development.json file to include the correct profile and region, it should contain:

{
    "AWS": {
        "Profile": "default",
        "Region": "ca-central-1"
    }
}

Step 9: Now go to the AWS Explorer


Make sure you have Canada (Central) selected as a Region
Right click on Amazon DynamoDB and click Create Table
Use Contacts as the table name and ContactID as the Hash Key Name with a Hash key Type of Numeric then click Create
(Note you could take this opportunity to change the primary ID into a string in the SQL database so that you can use GUID (string) keys in the new application, if you would like to do that, then create the HashKey as a string type and make sure before you convert your database to Dynamo that you change the data type on the ContactID column to varchar).
a

Step 10: At this point you should be able to set the API through Swagger, start the project using IIS Express (it will use a random port so adjust accordingly) and navigate to:

http://localhost:56475/swagger/index.html
Under the Contact API, click Put then click try it out, set some values in the model and change the content type to application/json then hit execute. You should see a screen that looks like this: a
This should return a 200 response code. Now you should be able to also run the Get method from the Contact API, click the GET button then Execute and you should see your data that you just added: a
So at this point you have a working serverless Web API created, you just need to reproduce the API.

Step 11: In the Visual Studio Solution Explorer, open the Pages folder, open the _Layout.cshtml file, after the line that says:

<li><a asp-page="/Index">Home</a></li>

add the following line that will link into a new page we will create:
<li><a asp-page="/Contacts">Contacts</a></li>

Step 12: Now, right click on the Pages folder, click on Add, New Item, click Web on the left hand side then click Razor, finally select Web Page (Razorv3) for the Razor Page name put Contacts, click Add

a

Step 13: Now paste in the code for the Contacts Razor Page:

@page
@{
    ViewData["Title"] = "Contacts";

}

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>My Site's Title</title>
    <script src="//code.jquery.com/jquery.min.js" type="text/javascript"></script>
    <script>

        function viewContact(id) {
            $.ajaxSetup({ cache: false });
           $.ajax({
                type: "GET",
                url: "./api/Contact/" + id,
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (data) {
                    document.getElementById("ID").value = data.id;
                    document.getElementById("Name").value = data.name;
                    document.getElementById("Mobile").value = data.mobile;
                    document.getElementById("Address").value = data.address;
                }
            });
        }

        function refreshTable() {
            $.ajaxSetup({ cache: false });
            $.ajax({
                type: "GET",
                url: "./api/Contact",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (data) {
                    //alert(JSON.stringify(data));
                    $("#DIV").html('');
                    var DIV = '';
                    $.each(data, function (i, item) {
                        var rows = "<tr>" +
                            "<td id='Name'>" + item.name + "</td>" +
                            "<td id='Mobile'>" + item.mobile + "</td>" +
                            "<td id='Address'>" + item.address + "</td>" +
                            "<td id='View'><a href=\"javascript:viewContact('" + item.id + "')\">View</a></td>" +
                            "</tr>";
                        $('#Table').append(rows);
                    }); //End of foreach Loop
                    console.log(data);
                }, //End of AJAX Success function

                failure: function (data) {
                    alert(data.responseText);
                }, //End of AJAX failure function
                error: function (data) {
                    alert(data.responseText);
                } //End of AJAX error function

            });


        };

        $(document).ready(refreshTable());



        $(function () {


            $('#btn-delete').on('click', function () {
                $.ajaxSetup({ cache: false });

                $.ajax({
                    type: 'DELETE',
                    url: './api/Contact/' + document.getElementById("ID").value,
                    contentType: "application/json",
                    success: function (json) {
                        if (!json.error) location.reload(true);
                    }
                });


            });
            $('#btn-clear').on('click', function () {
                document.getElementById("ID").value = -1;
                document.getElementById("Name").value = "";
                document.getElementById("Mobile").value = "";
                document.getElementById("Address").value = "";


            });
            $('#btn-update').on('click',  function () {
                $.ajaxSetup({ cache: false });
                var contact = { id: document.getElementById("ID").value, name: document.getElementById("Name").value, mobile: document.getElementById("Mobile").value, address: document.getElementById("Address").value };
                $.ajax({
                    type: 'POST',
                    data: JSON.stringify(contact),
                    url: './api/Contact',
                    contentType: "application/json",
                    success: function (json) {
                        if (!json.error) {
                            refreshTable();
                        }
                    }

                });

            });
        });
    </script>
</head>
<body>
    <form method="post">
        <fieldset>
            <br />
            <div>
                <input type="hidden" name="ID" id="ID" value=-1 />
                <label for="Name">Name:</label>
                <input type="text" name="Name" id="Name" value="" />
            </div>
            <div>
                <label for="Mobile">Mobile:</label>
                <input type="text" name="Mobile" id="Mobile" value="" />
            </div>
            <div>
                <label for="Address">Address:</label>
                <textarea rows="2" cols="40" name="Address" id="Address"></textarea>
            </div>
            <div>
                <button id="btn-update">Add/Update</button>
                <label>&nbsp;</label>
                <button id="btn-delete">Delete</button>
                <label>&nbsp;</label>
                <button id="btn-clear">Clear</button>
            </div>
            <label id="Success"></label>
            <br />
            <div class="panel-body">
                <table class="table table-bordered" id="Table">
                    <tr>
                        <th>Name</th>
                        <th>Mobile</th>
                        <th>Address</th>
                        <th>View</th>
                    </tr>   <!--end of table-row -->
                </table>   <!--end of table -->
            </div> <!--end of Panel-body -->
        </fieldset>
    </form>
</body>
</html>

Step 14: Now, test your application locally, start the project and navigate to the Contacts page and test the functionality.

Step 15: Now that your application is up and running locally, time to deploy to AWS. First let’s migrate the MS SQL database to DynamoDB.

In the AWS console, create a DynamoDB role for the migration, go to the IAM service, click on Roles, Create Role, for Service that will use this role select DMS, then click Next: Permissions a
On the permissions screen search for dynamo and select the AmazonDynamoDBFullAccess policy, click Next:Review a
Finally give this role the name DMSDynamoDBFull and click Create Role a
This will put you back at the roles page, find the DMSDynamoDBFull role and click on it, at the top of the page, copy the Role ARN
a

Step 16: Next, go to the AWS console, make sure you are in the Canada (Central) region, go to EC2, open the DMS service and click Get Started on the left hand side. Click Next past the start screen. Accept the defaults on the next page too by clicking Next

Configure the source and destination as follows: a
For the source engine use sqlserver
For the source IP use the private IP of the development box you are using
username: developer
password: ILove.Net!
database name: ReInvent2018
For the target engine use dynamodb copying in the arn of the dynamodb role you just created
Test both connections, once they both finish successfully, then click Next

On the next screen, give the task a name such as ReInvent-DMS-Task
At the bottom of the screen, under Table mappings, select the schema as dbo (it may take a minute or so for it to show up so), then click Add Selection Rule a
Click Create Task, this will put you in the Tasks window that shows the progress of the migration: a
Wait for the task to complete. a

Step 17: Create a dynamoDB role for lambda, go the AWS console, go to the IAM service, select Roles, Create Role, click Lambda then click Next: Permissions

a
In the policy screen, search for dynamodb and select the AmazonDynamoDBFullAccess – (In the real world, you would want to set a tight security boundary around access from lambda to DynamoDB). Click Next: Review a
Give the role the name LambdaDynamoDBFull and click Create Role Once it is created click on the role and copy the role ARN, you will need it for the next step.

Step 18: Go to Visual Studio, find the serverless.template file under Solution Explorer, add the role ARN to the role on line 22.

Step 19: Now in Visual Studio, right click on the project, click Publish to AWS Lambda


Select Canada (Central) as the region
Give the stack a name such as ReInventRefactor-2018
Click the New button next to the S3 Bucket and create a bucket for the source code, this will need to be unique (so add some random characters to it).

Then click Publish This will kick off a CloudFormation deploy of your code.
a

Step 20: You should get a publish screen similar to this:

a
Next the cloud formation status screen will show up, once it is complete click on the AWS Serverless URL and test your application, at this point everything should be working (if the application starts caching data open the URL in Chrome): a