Blazor - Connect With Amazon DynamoDB Using AWS SDK

DynamoDB Connectivity Unleashed

In this article, we will see how to create a Blazor application and connect with Amazon DynamoDB using AWS SDK.

I have already explained about Blazor framework in my previous C# Corner articles. Please refer below articles to get a basic idea about the Blazor framework.

Blazor is an experimental .NET web framework using C#/Razor and HTML that runs in the browser with WebAssembly. Blazor provides the benefits of a client-side web UI framework. NET.

In the first article, I explained how to connect the Blazor app with CosmosDB using “Microsoft.Azure.DocumentDB.Core” NuGet package. In the second article, I have explained how to connect Blazor with PostgreSQL using Entity Framework Core.

Here, we will see how to connect the Blazor app with an Amazon DynamoDB table.

Please refer to my previous article Getting Started With AWS Free Tier And Connect DynamoDB With .NET for more details about Amazon DynamoDB. I explained how to create a free AWS account for one year and how to connect DynamoDB with the .NET WinForms application. In this article, we will create a Student app in Blazor and will see all CRUD operations. We will use AWS SDK to connect DynamoDB with Blazor.

Please open Visual Studio 2017 (I am using the free community edition) and create a Blazor app. Choose an ASP.NET Core Web Application project template.

ASP.NET Core

We can choose the Blazor (ASP.NET Core hosted) template.

Template

Our solution will be ready in a moment. Please note that there are three projects created in our solution - “Client”, “Server”, and “Shared”.The client project contains all the client-side libraries and Razor Views, while the server project contains the Web API Controller and other business logic. The shared project contains commonly shared files, like models and interfaces.

 Web API

By default, Blazor created many files in these three projects. We can remove all the unwanted files like “Counter.cshtml”, “FetchData.cshtml”, “SurveyPrompt.cshtml” from the Client project and the “SampleDataController.cs” file from the Server project and delete the “WeatherForecast.cs” file from the shared project too.

We can add “AWSSDK.DynamoDBv2” NuGet package to Shared project and server project. Both projects need this package. This SDK is used for creating connectivity between the Blazor app and DynamoDB.

Blazor

As I told you earlier, we will create a Student app. First, we can create a student model in a shared project. Please create a “Models” folder and create a Student class file inside this folder.

Student. cs

using Amazon.DynamoDBv2.DataModel;

namespace BlazorDynamoDBStudentApp.Shared.Models 
{   
    [DynamoDBTable("Student")]
    public class Student 
    {   
        [DynamoDBHashKey]
        public string StudentId 
        {   
            get;   
            set;   
        }   

        public string Name 
        {   
            get;   
            set;   
        }   

        public string Class 
        {   
            get;   
            set;   
        }   

        public int Physics 
        {   
            get;   
            set;   
        }   

        public int Maths 
        {   
            get;   
            set;   
        }   

        public int Chemistry 
        {   
            get;   
            set;   
        }   

        public int IsDeleted 
        {   
            get;   
            set;   
        }   
    }   
}   

Please note that we have used the “DynamoDBTable” attribute to the Student class and the “DynamoDBHashKey” attribute to the StudentId property in this class. This will create a Hash key (Primary Key) in DynamoDB while creating the table.

We are using an Interface to define all the CRUD operations for our application. Please create the “IDataAccessProvider” interface and copy the below code to this file.

IDataAccessProvider.cs

using System.Collections.Generic;
using System.Threading.Tasks;

namespace BlazorDynamoDBStudentApp.Shared.Models
{
    public interface IDataAccessProvider
    {
        Task AddStudentRecord(Student student);
        Task UpdateStudentRecord(Student student);
        Task DeleteStudentRecord(string studentId);
        Task<Student> GetStudentSingleRecord(string studentId);
        Task<IEnumerable<Student>> GetStudentRecords();
    }
}

We have defined the signature for our DataAcess class in this Interface. We can create a new folder “DataAccess” in the Server project and create the “DynamoDBInitializer” class.

DynamoDBInitializer.cs

using Amazon;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace BlazorDynamoDBStudentApp.Server.DataAccess
{
    public static class DynamoDBInitializer
    {
        private const string accessKey = "Your access Key";
        private const string secretKey = "Your secret Key";
        public static AmazonDynamoDBClient client;

        public static async Task InitializeDynamoDB()
        {
            string tableName = "Student";
            string hashKey = "StudentId";

            // Creating credentials and initializing DynamoDB client
            var credentials = new BasicAWSCredentials(accessKey, secretKey);
            client = new AmazonDynamoDBClient(credentials, RegionEndpoint.APSouth1);

            // Verify table
            var tableResponse = await client.ListTablesAsync();
            if (!tableResponse.TableNames.Contains(tableName))
            {
                // Table not found, creating table
                await client.CreateTableAsync(new CreateTableRequest
                {
                    TableName = tableName,
                    ProvisionedThroughput = new ProvisionedThroughput
                    {
                        ReadCapacityUnits = 3,
                        WriteCapacityUnits = 1
                    },
                    KeySchema = new List<KeySchemaElement>
                    {
                        new KeySchemaElement
                        {
                            AttributeName = hashKey,
                            KeyType = KeyType.HASH
                        }
                    },
                    AttributeDefinitions = new List<AttributeDefinition>
                    {
                        new AttributeDefinition
                        {
                            AttributeName = hashKey,
                            AttributeType = ScalarAttributeType.S
                        }
                    }
                });

                bool isTableAvailable = false;
                while (!isTableAvailable)
                {
                    //"Waiting for table to be active...
                    Thread.Sleep(5000);
                    var tableStatus = await client.DescribeTableAsync(tableName);
                    isTableAvailable = tableStatus.Table.TableStatus == "ACTIVE";
                }
            }
        }
    }
}

This is a static class. We have given the AWS access key and secret key inside this class. (You may create a config file for this purpose.)

We will call this static class while initializing the application. We have given the DynamoDB Table name inside this class. Every time the application runs, this class will check the existence of the DynamoDB Table and if not exist, it will create a new Table using the given credentials.

It also creates a static “AmazonDynamoDBClient” client variable and it will be used for creating AWS context in all CRUD operations.

We can create a “DataAccessDynamoDBProvider” class now. This class will implement all the CRUD actions we defined in the IDataAccessProvider interface.

DataAccessDynamoDBProvider.cs

using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using BlazorDynamoDBStudentApp.Shared.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorDynamoDBStudentApp.Server.DataAccess
{
    public class DataAccessDynamoDBProvider : IDataAccessProvider
    {
        private readonly ILogger _logger;

        public DataAccessDynamoDBProvider(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("DataAccessDynamoDBProvider");
        }

        public async Task AddStudentRecord(Student student)
        {
            // Set a local DB context
            var context = new DynamoDBContext(DynamoDBInitializer.client);
            student.StudentId = Guid.NewGuid().ToString();
            student.IsDeleted = 0;
            // Save a Student object
            await context.SaveAsync<Student>(student);
        }

        public async Task UpdateStudentRecord(Student student)
        {
            // Set a local DB context
            var context = new DynamoDBContext(DynamoDBInitializer.client);
            // Getting a Student object
            List<ScanCondition> conditions = new List<ScanCondition>();
            conditions.Add(new ScanCondition("StudentId", ScanOperator.Equal, student.StudentId));
            var allDocs = await context.ScanAsync<Student>(conditions).GetRemainingAsync();
            var editedState = allDocs.FirstOrDefault();
            if (editedState != null)
            {
                editedState = student;
                // Save a Student object
                await context.SaveAsync<Student>(editedState);
            }
        }

        public async Task DeleteStudentRecord(string studentId)
        {
            const string tableName = "Student";
            var request = new DeleteItemRequest
            {
                TableName = tableName,
                Key = new Dictionary<string, AttributeValue>()
                {
                    {
                        "StudentId",
                        new AttributeValue
                        {
                            S = studentId
                        }
                    }
                }
            };
            var response = await DynamoDBInitializer.client.DeleteItemAsync(request);
        }

        public async Task<Student> GetStudentSingleRecord(string id)
        {
            var context = new DynamoDBContext(DynamoDBInitializer.client);
            // Getting a Student object
            List<ScanCondition> conditions = new List<ScanCondition>();
            conditions.Add(new ScanCondition("StudentId", ScanOperator.Equal, id));
            var allDocs = await context.ScanAsync<Student>(conditions).GetRemainingAsync();
            var student = allDocs.FirstOrDefault();
            return student;
        }

        public async Task<IEnumerable<Student>> GetStudentRecords()
        {
            var context = new DynamoDBContext(DynamoDBInitializer.client);
            // Getting a Student object
            List<ScanCondition> conditions = new List<ScanCondition>();
            conditions.Add(new ScanCondition("IsDeleted", ScanOperator.Equal, 0));
            var allDocs = await context.ScanAsync<Student>(conditions).GetRemainingAsync();
            return allDocs;
        }
    }
}

I have defined all the CRUD operations inside this class. All methods are asynchronous and self-explanatory. We will call these methods from our Web API controller.

We can add the “StudentsController” controller now. This API controller will call all the methods in the DataAccessDynamoDBProvider class.

StudentsController.cs

using BlazorDynamoDBStudentApp.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace BlazorDynamoDBStudentApp.Server.Controllers
{
    public class StudentsController : Controller
    {
        private readonly IDataAccessProvider _dataAccessProvider;

        public StudentsController(IDataAccessProvider dataAccessProvider)
        {
            _dataAccessProvider = dataAccessProvider;
        }

        [HttpGet]
        [Route("api/Students/Get")]
        public async Task<IEnumerable<Student>> Get()
        {
            return await _dataAccessProvider.GetStudentRecords();
        }

        [HttpPost]
        [Route("api/Students/Create")]
        public async Task Create([FromBody] Student student)
        {
            if (ModelState.IsValid)
            {
                await _dataAccessProvider.AddStudentRecord(student);
            }
        }

        [HttpGet]
        [Route("api/Students/Details/{id}")]
        public async Task<Student> Details(string id)
        {
            return await _dataAccessProvider.GetStudentSingleRecord(id);
        }

        [HttpPut]
        [Route("api/Students/Edit")]
        public async Task Edit([FromBody] Student student)
        {
            if (ModelState.IsValid)
            {
                await _dataAccessProvider.UpdateStudentRecord(student);
            }
        }

        [HttpDelete]
        [Route("api/Students/Delete/{studentId}")]
        public async Task DeleteConfirmed(string studentId)
        {
            await _dataAccessProvider.DeleteStudentRecord(studentId);
        }
    }
}

We have defined all the HTTP, GET, PUT, DELETE, and POST methods for our CRUD actions inside this controller. Please note all these methods are asynchronous.

We can modify the “ConfigureServices” method in the Startup class in the Server project.

Please inject the dependency for the “DataAccessDynamoDBProvider” class inside this method.

We must call the “InitializeDynamoDB” from the “DynamoDBInitializer” class also in this method.

InitializeDynamoDB

When our application starts, this dependency will be injected into the service.

We have added all the files for the shared and server projects. We can now add the Razor views for our Client project. Please refer my previous Blazor articles for more details about Razor views.

We can modify the “NavMenu.cshtml” now. This is a Razor view file and is used for controlling the navigation.

NavMenu.cshtml

<p class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">Student App</a>
    <button class="navbar-toggler" onclick=@ToggleNavMenu>
        <span class="navbar-toggler-icon"></span>
    </button>
</p>
<p class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match=NavLinkMatch.All>
                <span class="oi oi-home" aria-hidden="true"></span> Home </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="/liststudents">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Student Details </NavLink>
        </li>
    </ul>
</p>
@functions
{
    bool collapseNavMenu = true;
    void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

We already have a NavMenu.cshtml file under the Shared folder but replace it with the above code.

We can add a “ListStudents.cshtml” Razor view file under the “Pages” folder now.

ListStudents.cshtml

@using BlazorDynamoDBStudentApp.Shared.Models
@page "/liststudents"
@inject HttpClient Http

<h1>Student Details</h1>
<p>
    <a href="/addstudent">Create New Student</a>
</p>
@if (studentList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class='table'>
        <thead>
            <tr>
                <th>Name</th>
                <th>Class</th>
                <th>Physics</th>
                <th>Chemistry</th>
                <th>Maths</th>
                <th>Total</th>
                <th>Average</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var student in studentList)
            {
                total = @student.Maths + @student.Physics + @student.Chemistry;
                avg = (@student.Maths + @student.Physics + @student.Chemistry) / 3;
                <tr>
                    <td>@student.Name </td>
                    <td>@student.Class</td>
                    <td>@student.Physics</td>
                    <td>@student.Chemistry</td>
                    <td>@student.Maths</td>
                    <td>@total</td>
                    <td>@avg</td>
                    <td>
                        <a href='/editstudent/@student.StudentId'>Edit</a>
                        <a href='/deletestudent/@student.StudentId'>Delete</a>
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

@functions {
    Student[] studentList;
    int total;
    float avg;

    protected override async Task OnInitAsync()
    {
        studentList = await Http.GetJsonAsync<Student[]>("/api/Students/Get");
    }
}

The above Razor view file will display all the student information in an HTML table format.

We can add the “AddStudent.cshtml” view now.

AddStudent.cshtml

@using BlazorDynamoDBStudentApp.Shared.Models
@page "/addstudent"
@inject HttpClient Http
@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper

<h2>Create Student</h2>
<hr />
<p class="row">
    <p class="col-md-4">
        <form>
            <p class="form-group">
                <label for="Name" class="control-label">Name</label>
                <input for="Name" class="form-control" bind="@student.Name" />
            </p>
            <p class="form-group">
                <label for="Class" class="control-label">Class</label>
                <input for="Class" class="form-control" bind="@student.Class" />
            </p>
            <p class="form-group">
                <label for="Physics" class="control-label">Physics</label>
                <input for="Physics" class="form-control" bind="@student.Physics" />
            </p>
            <p class="form-group">
                <label for="Chemistry" class="control-label">Chemistry</label>
                <input for="Chemistry" class="form-control" bind="@student.Chemistry" />
            </p>
            <p class="form-group">
                <label for="Maths" class="control-label">Maths</label>
                <input for="Maths" class="form-control" bind="@student.Maths" />
            </p>
            <p class="form-group">
                <input type="button" class="btn btn-default" onclick="@(async () => await CreateStudent())" value="Save" />
                <input type="button" class="btn" onclick="@Cancel" value="Cancel" />
            </p>
        </form>
    </p>
</p>

@functions {
    
    Student student = new Student();
    
    protected async Task CreateStudent()
    {
        await Http.SendJsonAsync(HttpMethod.Post, "/api/Students/Create", student);
        UriHelper.NavigateTo("/liststudents");
    }
    
    void Cancel()
    {
        UriHelper.NavigateTo("/liststudents");
    }
}

Please add below two Razor view files inside the Pages folder.

EditStudent.cshtml

@using BlazorDynamoDBStudentApp.Shared.Models
@page "/editstudent/{StudentId}"
@inject HttpClient Http
@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper

<h2>Edit</h2>
<h4>Student</h4>
<hr />

<p class="row">
    <p class="col-md-4">
        <form>
            <p class="form-group">
                <label for="Name" class="control-label">Name</label>
                <input for="Name" class="form-control" bind="@student.Name" />
            </p>
            <p class="form-group">
                <label for="Class" class="control-label">Class</label>
                <input for="Class" class="form-control" bind="@student.Class" />
            </p>
            <p class="form-group">
                <label for="Physics" class="control-label">Physics</label>
                <input for="Physics" class="form-control" bind="@student.Physics" />
            </p>
            <p class="form-group">
                <label for="Chemistry" class="control-label">Chemistry</label>
                <input for="Chemistry" class="form-control" bind="@student.Chemistry" />
            </p>
            <p class="form-group">
                <label for="Maths" class="control-label">Maths</label>
                <input for="Maths" class="form-control" bind="@student.Maths" />
            </p>
            <p class="form-group">
                <input type="button" value="Save" onclick="@(async () => await UpdateStudent())" class="btn btn-default" />
                <input type="button" value="Cancel" onclick="@Cancel" class="btn" />
            </p>
        </form>
    </p>
</p>

@functions {
    [Parameter]
    string StudentId { get; set; }

    Student student = new Student();

    protected override async Task OnInitAsync()
    {
        student = await Http.GetJsonAsync<Student>("/api/Students/Details/" + StudentId);
    }

    protected async Task UpdateStudent()
    {
        await Http.SendJsonAsync(HttpMethod.Put, "api/Students/Edit", student);
        UriHelper.NavigateTo("/liststudents");
    }

    void Cancel()
    {
        UriHelper.NavigateTo("/liststudents");
    }
}

DeleteStudent.cshtml

@using BlazorDynamoDBStudentApp.Shared.Models
@page "/deletestudent/{studentId}"
@inject HttpClient Http
@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper UriHelper

<h2>Delete</h2>
<p>Are you sure you want to delete this Student with id: <b>@studentId</b></p>
<br />
<p class="col-md-4">
    <table class="table">
        <tr>
            <td>Name</td>
            <td>@student.Name</td>
        </tr>
        <tr>
            <td>Class</td>
            <td>@student.Class</td>
        </tr>
        <tr>
            <td>Physics</td>
            <td>@student.Physics</td>
        </tr>
        <tr>
            <td>Chemistry</td>
            <td>@student.Chemistry</td>
        </tr>
        <tr>
            <td>Maths</td>
            <td>@student.Maths</td>
        </tr>
    </table>
    <p class="form-group">
        <input type="button" value="Delete" onclick="@(async () => await Delete())" class="btn btn-default" />
        <input type="button" value="Cancel" onclick="@Cancel" class="btn" />
    </p>
</p>

@functions {
    
    [Parameter]
    string studentId { get; set; }
    
    Student student = new Student();
    
    protected override async Task OnInitAsync()
    {
        student = await Http.GetJsonAsync<Student>("/api/Students/Details/" + studentId);
    }
    
    protected async Task Delete()
    {
        await Http.DeleteAsync("api/Students/Delete/" + studentId);
        UriHelper.NavigateTo("/liststudents");
    }
    
    void Cancel()
    {
        UriHelper.NavigateTo("/liststudents");
    }
}

We have completed all the coding parts. We can check the application. Please run the application. Our home page looks like below.

Home page

We can create a Student record now.

Student record

Please note that StudentId will be created automatically by the system. I used GUID to create StudentId.

We can create two more student records now. Our records will be displayed below. I have additionally added the Total and Average of three marks in Physics, Chemistry, and Maths.

Student details

We can try to edit one student record now.

Edit

I have edited the name of the student.

We can delete one student record now.

Delete

We have seen all the CRUD actions in this app.

We can now open the AWS Management Console and see the DynamoDB table and items (records) there. Please open Services and DynamoDB from the database tab. You can see the student table there. We have created the table with StudentId as a partition (Primary) key. Please click the student table.

AWS Management

We can see the previously created records there. (We already deleted one student record),

Create item

You can edit the record in the AWS console also. For that, please click the partition key. Here StudentId is the partition key. Please click on that link.

Edit item

We can take the backup of our table on a regular basis. You can even maintain a continuous backup also.

Create backup

Please give a name to your backup.

Table backup

We can restore the data from this backup file when needed. We can create a new table also from this backup file.

In this article, we have created a Blazor app using an ASP.NET Core-hosted template. After that, we created a DynamoDB table using AWS SDK. We inserted, edited, and deleted Student data from DynamoDB. We also created a DynamoDB table backup in AWS Management Console.

We will see more exciting features of Blazor and DynamoDB in upcoming articles. Reference articles on Blazor and Amazon DynamoDB in C# Corner are mentioned below.


Similar Articles