2/16/2020 Admin

Creating A Step-By-Step End-To-End Database Client-Side (WebAssembly) Blazor Application



In this article, using client (WebAssembly) Blazor, we will demonstrate how a list of Weather forecasts can be added to the database by each user. A user will only have the ability to see their own forecasts.


Walk-Thru

image

When we navigate to the Fetch data page, without being logged in, it will indicate that you Must be logged in.

To log in, click the Log In link.


image

You can log in with an Azure Active Directory account that has authorization to log into the Azure registered application.


image

Once a user is logged in, navigating to the Fetch data page displays their data.

The logged in user’s name is also displayed at the top of the application.

The user can add or edit Weather Forecasts that will be saved to the database.


Starting With The Blazor Client AD Code

image

For this tutorial, we will start with the code from the article: Client Side Blazor Authentication Using Azure AD and a Custom AuthenticationStateProvider.


Use SQL Server

image

You can create a database using SQL Server Express LocalDB. However, it can be problematic to install and configure. Using the free SQL Server 2019 Developer server (or the full SQL Server) is recommended.

Download and install SQL Server 2019 Developer Edition from the following link:

https://www.microsoft.com/en-us/sql-server/sql-server-downloads


Create The Database

image

Open the SQL Server Object Explorer.


image

Add a connection to your local database server if you don’t already have it in the SQL Server list.

For this tutorial, we do not want to use the SQL Express server on (localdb) that you may already see in the list.


image

You will specify just the server and Connect.


image

Expand the tree under the local SQL server, right-click on the Databases folder and select Add New Database.


image

Give the database a name and press Enter.

The database will be created.


image

Right-click on the Tables node and select Add New Table.


image

Paste the following script in the T-SQL window and then click the Update button:


								
									CREATE
									TABLE
									[dbo].[WeatherForecast] (
								
									[Id]
									INT
									IDENTITY
									(1, 1)
									NOT
									NULL,
								
									[Date]
									DATETIME
									NULL,
								
									[TemperatureC]
									INT
									NULL,
								
									[TemperatureF]
									INT
									NULL,
								
									[Summary]
									NVARCHAR
									(50)
									NULL,
								
									[UserName]
									NVARCHAR
									(50)
									NULL,
								
									PRIMARY
									KEY
									CLUSTERED
									([Id]
									ASC)
								
);
								

							
								

								

							

image

The script will prepare.

Click the Update Database button.


image

Back in the Server Explorer window, right-click on Tables and select Refresh.


image

The WeatherForecast table will display.


image

Right-click on the table and select Show Table Data.


image

We will enter some sample data so that we will be able to test the data connection in the next steps.

Set the UserName field to the username of the user that we plan to log in with.


image

Right-Click on the Database node and select Properties.


image

The Properties window for the database will display.

Copy the Connection string for the database.


image

In the Server project, open the appsettings.json file.


image

Add a database connection string like this for the DefaultConnection (replacing {{ Your Connection String}} with the connection string you copied), and save the file:


								
									"ConnectionStrings": {
								
									"DefaultConnection": "{{ Your Connection String }}"
								
									},


Create The Data Context

image

If you do not already have it installed, install EF Core Power Tools from:

https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools


image

(Note: Before installing, close Visual Studio)

(Note: Please give this project a 5 star review on marketplace.visualstudio.com!)


image

Right-click on the project node in the Solution Explorer and select EF Core Power Tools then Reverse Engineer.


image

Click the Add button.


image

Connect to the database.


image

Select the database connection in the dropdown and click OK.


image

Select the WeatherForecast table and click OK.


image

Set the values and click OK.


image

The Data Context will be created.

Click the OK button to close the popup.


image

In the Solution Explorer, you will see the files for the Data Context.


Update the Startup.cs File

image

Open the Startup.cs file in the Server project.

Add the following using statements:


								
									using
									Microsoft.EntityFrameworkCore;
								
									using
									EndToEndDB.Data.EndToEnd;


Add the following code to the top of the ConfigureServices method:


								
									// Read the connection string from the appsettings.json file
								
									// Set the database connection for the EndtoEndContext
								
									services.AddDbContext<EndToEndDB.Data.EndToEnd.EndtoEndContext>(options =>
								
									options.UseSqlServer(
								
									Configuration.GetConnectionString("DefaultConnection")));



Create The Weather Forecast DTO

image

Because the EF Core Power Tools created a WeatherForecast.cs class for the Data Context, we now have two files of the same class.

Delete the WeatherForecast.cs class that is in the Shared project.


image

Replace it with a file name WeatherForecastDTO.cs using the following code:


								
									using
									System;
								
									using
									System.Collections.Generic;
								
									using
									System.Text;
								

								
									namespace
									BlazorClientAD.Shared
								
{
								
									public
									class
									WeatherForecastDTO
								
									{
								
									public
									int
									Id {
									get;
									set; }
								
									public
									DateTime? Date {
									get;
									set; }
								
									public
									int? TemperatureC {
									get;
									set; }
								
									public
									int? TemperatureF {
									get;
									set; }
								
									public
									string
									Summary {
									get;
									set; }
								
									public
									string
									UserName {
									get;
									set; }
								
									}
								
}
								

							

The Controller

image

The Blazor client side code communicates with the server side code through the controller.

Open the WeatherForecastController.cs file in the server project and replace all the code with the following:



								
									using
									System;
								
									using
									System.Collections.Generic;
								
									using
									System.Linq;
								
									using
									System.Threading.Tasks;
								
									using
									Microsoft.AspNetCore.Mvc;
								
									using
									Microsoft.AspNetCore.Authorization;
								
									using
									EndToEndDB.Data.EndToEnd;
								
									using
									Microsoft.EntityFrameworkCore;
								
									using
									BlazorClientAD.Shared;
								

								
									namespace
									BlazorClientAD.Server.Controllers
								
{
								
									// [Authorize] means this can only be called by logged in users
								
									[Authorize]
								
									[ApiController]
								
									public
									class
									WeatherForecastController : ControllerBase
								
									{
								
									// This holds the Data Context object to communicate
								
									// with the database
								
									private
									readonly
									EndtoEndContext _context;
								

								
									// Inject the EndtoEndContext using dependency injection
								
									public
									WeatherForecastController(EndtoEndContext context)
								
									{
								
									_context = context;
								
									}
								

								
									[HttpGet]
								
									[Route("api/WeatherForecast/GetAsync")]
								
									public
									async Task<List<WeatherForecastDTO>>
								
									GetAsync()
								
									{
								
									// Get the current user
								
									string
									strCurrentUser =
									this.User.Identity.Name;
								

								
									// Get Weather Forecasts
									
								
									var result = await _context.WeatherForecast
								
									// Only get entries for the current logged in user
								
									.Where(x => x.UserName == strCurrentUser)
								
									// Use AsNoTracking to disable EF change tracking
								
									// Use ToListAsync to avoid blocking a thread
								
									.AsNoTracking().ToListAsync();
								

								
									// Collection to return
								
									List<WeatherForecastDTO> ColWeatherForecast =
								
									new
									List<WeatherForecastDTO>();
								

								
									// Loop through the results
								
									foreach
									(var item
									in
									result)
								
									{
								
									// Create a new WeatherForecast instance
								
									WeatherForecastDTO objWeatherForecastDTO =
								
									new
									WeatherForecastDTO();
								

								
									// Set the values for the WeatherForecast instance
								
									objWeatherForecastDTO.Id =
								
									item.Id;
								
									objWeatherForecastDTO.Date =
								
									item.Date;
								
									objWeatherForecastDTO.UserName =
								
									item.UserName;
								
									objWeatherForecastDTO.TemperatureF =
								
									item.TemperatureF;
								
									objWeatherForecastDTO.TemperatureC =
								
									item.TemperatureC;
								
									objWeatherForecastDTO.Summary =
								
									item.Summary;
								

								
									// Add the WeatherForecast instance to the collection
								
									ColWeatherForecast.Add(objWeatherForecastDTO);
								
									}
								

								
									// Return the final collection
								
									return
									ColWeatherForecast;
								
									}
								

								
									[HttpPost]
								
									[Route("api/WeatherForecast/Post")]
								
									public
									void
								
									Post([FromBody] WeatherForecastDTO paramWeatherForecast)
								
									{
								
									// Create a new WeatherForecast instance
								
									WeatherForecast objWeatherForecast =
								
									new
									WeatherForecast();
								

								
									// Get the current user
								
									string
									strCurrentUser =
									this.User.Identity.Name;
								

								
									// Set the values for the WeatherForecast instance
								
									objWeatherForecast.Id =
								
									paramWeatherForecast.Id;
								
									objWeatherForecast.Date =
								
									paramWeatherForecast.Date;
								
									objWeatherForecast.UserName =
								
									strCurrentUser;
								
									objWeatherForecast.TemperatureF =
								
									paramWeatherForecast.TemperatureF;
								
									objWeatherForecast.TemperatureC =
								
									paramWeatherForecast.TemperatureC;
								
									objWeatherForecast.Summary =
								
									paramWeatherForecast.Summary;
								

								
									// Save to the database
								
									_context.WeatherForecast.Add(objWeatherForecast);
								
									_context.SaveChanges();
								
									}
								

								
									[HttpPut]
								
									[Route("api/WeatherForecast/Put")]
								
									public
									void
								
									Put([FromBody] WeatherForecastDTO objWeatherForecast)
								
									{
								
									// Get the current user
								
									string
									strCurrentUser =
									this.User.Identity.Name;
								

								
									// Find the existing Weather Forecast record
									
								
									// using the id
								
									var ExistingWeatherForecast =
								
									_context.WeatherForecast
								
									.Where(x => x.Id == objWeatherForecast.Id)
								
									.Where(x => x.UserName == strCurrentUser)
								
									.FirstOrDefault();
								

								
									if
									(ExistingWeatherForecast !=
									null)
								
									{
								
									// Update the values
								
									ExistingWeatherForecast.Date =
								
									objWeatherForecast.Date;
								
									ExistingWeatherForecast.Summary =
								
									objWeatherForecast.Summary;
								
									ExistingWeatherForecast.TemperatureC =
								
									objWeatherForecast.TemperatureC;
								
									ExistingWeatherForecast.TemperatureF =
								
									objWeatherForecast.TemperatureF;
								

								
									// Save to the database
								
									_context.SaveChanges();
								
									}
								
									}
								

								
									[HttpDelete]
								
									[Route("api/WeatherForecast/Delete/{id}")]
								
									public
									void
								
									Delete(int
									id)
								
									{
								
									// Get the current user
								
									string
									strCurrentUser =
									this.User.Identity.Name;
								

								
									// Find the existing Weather Forecast record
									
								
									// using the id
								
									var ExistingWeatherForecast =
								
									_context.WeatherForecast
								
									.Where(x => x.Id == id)
								
									.Where(x => x.UserName == strCurrentUser)
								
									.FirstOrDefault();
								

								
									if
									(ExistingWeatherForecast !=
									null)
								
									{
								
									// Remove from the database
								
									_context.WeatherForecast
								
									.Remove(ExistingWeatherForecast);
								
									_context.SaveChanges();
								
									}
								
									}
								
									}
								
}




Client Code

image

In the Client project, open the FetchData.razor page and replace all the code with the following code:


								
@page "/fetchdata"
								
@using BlazorClientAD.Shared
								
@inject HttpClient Http
								
@inject AuthenticationStateProvider AuthenticationStateProvider
								

								
									<
									h1
									>Weather forecast</
									h1
									>
								
									<!-- AuthorizeView allows us to only show sections of the page -->
								
									<!-- based on the security on the current user -->
								
									<
									AuthorizeView
									>
								
									<!-- Show this section if the user is logged in -->
								
									<
									Authorized
									>
								
									@if (forecasts == null)
								
									{
								
									<!-- Show this if the current user has no data... yet... -->
								
									<
									p
									>
									<
									em
									>Loading...</
									em
									>
									</
									p
									>
								
									}
								
									else
								
									{
								
									<!-- Show the forecasts for the current user -->
								
									<
									table
									class="table"
									>
								
									<
									thead
									>
								
									<
									tr
									>
								
									<
									th
									>Date</
									th
									>
								
									<
									th
									>Temp. (C)</
									th
									>
								
									<
									th
									>Temp. (F)</
									th
									>
								
									<
									th
									>Summary</
									th
									>
								
									<
									th
									>
									</
									th
									>
								
									</
									tr
									>
								
									</
									thead
									>
								
									<
									tbody
									>
								
									@foreach (var forecast in forecasts)
								
									{
								
									<
									tr
									>
								
									<
									td
									>@forecast.Date.Value.ToShortDateString()</
									td
									>
								
									<
									td
									>@forecast.TemperatureC</
									td
									>
								
									<
									td
									>@forecast.TemperatureF</
									td
									>
								
									<
									td
									>@forecast.Summary</
									td
									>
								
									<
									td
									>
								
									<!-- Edit the current forecast -->
								
									<
									button
									class="btn btn-primary"
								
									@onclick="(() =>
									
								
									EditForecast(forecast))">
								
									Edit
								
									</
									button
									>
								
									</
									td
									>
								
									</
									tr
									>
								
									}
								
									</
									tbody
									>
								
									</
									table
									>
								
									<
									p
									>
								
									<!-- Add a new forecast -->
								
									<
									button
									class="btn btn-primary"
								
									@onclick="AddNewForecast"
									>
								
									Add New Forecast
								
									</
									button
									>
								
									</
									p
									>
								

								

								
									@if (ShowPopup)
								
									{
								
									<!-- This is the popup to create or edit a forecast -->
								
									<
									div
									class="modal"
									tabindex="-1"
									style="display:block"
								
									role="dialog"
									>
								
									<
									div
									class="modal-dialog"
									>
								
									<
									div
									class="modal-content"
									>
								
									<
									div
									class="modal-header"
									>
								
									<
									h3
									class="modal-title"
									>Edit Forecast</
									h3
									>
								
									<!-- Button to close the popup -->
								
									<
									button
									type="button"
									class="close"
								
									@onclick="ClosePopup"
									>
								
									<
									span
									aria-hidden="true"
									>X</
									span
									>
								
									</
									button
									>
								
									</
									div
									>
								
									<!-- Edit form for the current forecast -->
								
									<
									div
									class="modal-body"
									>
								
									<
									input
									class="form-control"
									type="text"
								
									placeholder="Celsius forecast"
								
									@bind="objWeatherForecastDTO
									
								
									.TemperatureC"
									/>
								
									<
									input
									class="form-control"
									type="text"
								
									placeholder="Fahrenheit forecast"
								
									@bind="objWeatherForecastDTO
									
								
									.TemperatureF"
									/>
								
									<
									input
									class="form-control"
									type="text"
								
									placeholder="Summary"
								
									@bind="objWeatherForecastDTO.Summary"
									/>
								
									<
									br
									/>
								
									<!-- Button to save the forecast -->
								
									<
									button
									class="btn btn-primary"
								
									@onclick="SaveForecast"
									>
								
									Save
								
									</
									button
									>
								
									<!-- Only show button if not a new record -->
								
									@if (objWeatherForecastDTO.Id > 0)
								
									{
								
									<!-- Button to delete the forecast -->
								
									<
									button
									class="btn btn-primary"
								
									@onclick="DeleteForecast"
									>
								
									Delete
								
									</
									button
									>
								
									}
								
									</
									div
									>
								
									</
									div
									>
								
									</
									div
									>
								
									</
									div
									>
								
									}
								
									}
								
									</
									Authorized
									>
								
									<!-- Show this section if the user is not logged in -->
								
									<
									NotAuthorized
									>
								
									<
									p
									>You're not signed in.</
									p
									>
								
									</
									NotAuthorized
									>
								
									</
									AuthorizeView
									>
								

							


Add the following C# code to support the page markup:


								
									@code
									{
								
									// This passes authentication to the control
								
									// to allow us to determine the current user
								
									// This can be 'altered' by a user so we do
								
									// not trust this value in server side code
								
									[CascadingParameter]
								
									private
									Task<AuthenticationState>
								
									authenticationStateTask {
									get;
									set; }
								

								
									// Stores the forecasts displayed in a list
								
									private
									List<WeatherForecastDTO> forecasts;
								

								
									// Stores a single forecast
								
									WeatherForecastDTO objWeatherForecastDTO =
								
									new
									WeatherForecastDTO();
								

								
									// Controls if the popup is displayed
								
									bool
									ShowPopup =
									false;
								

								
									// First method to run when user navicates to this control
								
									protected
									override
									async Task OnInitializedAsync()
								
									{
								
									// Get the current user
								
									var authState = await authenticationStateTask;
								
									var user = authState.User;
								
									if
									(user.Identity !=
									null)
								
									{
								
									if
									(user.Identity.IsAuthenticated)
								
									{
								
									// Make a call to get the forecasts
								
									// we don't pass the user because the server
								
									// side code will determine who the user is
									
								
									// from the authentication cookie
								
									forecasts =
								
									await Http
								
									.GetJsonAsync<List<WeatherForecastDTO>>(
								
									"/api/WeatherForecast/GetAsync");
								
									}
								
									}
								
									}
								

								
									void
									ClosePopup()
								
									{
								
									// Close the Popup
								
									ShowPopup =
									false;
								
									}
								

								
									void
									AddNewForecast()
								
									{
								
									// Make new forecast
								
									objWeatherForecastDTO =
									new
									WeatherForecastDTO();
								
									// Set Id to 0 so we know it is a new record
								
									objWeatherForecastDTO.Id = 0;
								
									// Open the Popup
								
									ShowPopup =
									true;
								
									}
								

								
									async Task SaveForecast()
								
									{
								
									// Close the Popup
								
									ShowPopup =
									false;
								
									// Get the current user
								
									var user = (await authenticationStateTask).User;
								
									// A new forecast will have the Id set to 0
								
									if
									(objWeatherForecastDTO.Id == 0)
								
									{
								
									// Create new forecast
								
									WeatherForecastDTO objNewWeatherForecastDTO =
								
									new
									WeatherForecastDTO();
								
									objNewWeatherForecastDTO.Date =
								
									System.DateTime.Now;
								
									objNewWeatherForecastDTO.Summary =
								
									objWeatherForecastDTO.Summary;
								
									objNewWeatherForecastDTO.TemperatureC =
								
									Convert.ToInt32(objWeatherForecastDTO.TemperatureC);
								
									objNewWeatherForecastDTO.TemperatureF =
								
									Convert.ToInt32(objWeatherForecastDTO.TemperatureF);
								
									// Username will be set server side
								
									objNewWeatherForecastDTO.UserName = "";
								

								
									// Save the result
								
									await Http.SendJsonAsync(
								
									HttpMethod.Post,
								
									"/api/WeatherForecast/Post",
								
									objNewWeatherForecastDTO);
								
									}
								
									else
								
									{
								
									// This is an update
								
									await Http.SendJsonAsync(
								
									HttpMethod.Put,
								
									"/api/WeatherForecast/Put",
								
									objWeatherForecastDTO);
								
									}
								

								
									// Get the forecasts for the current user
								
									forecasts =
								
									await Http
								
									.GetJsonAsync<List<WeatherForecastDTO>>(
								
									"/api/WeatherForecast/GetAsync");
								
									}
								

								
									void
									EditForecast(WeatherForecastDTO WeatherForecastDTO)
								
									{
								
									// Set the selected forecast
								
									// as the current forecast
								
									objWeatherForecastDTO = WeatherForecastDTO;
								
									// Open the Popup
								
									ShowPopup =
									true;
								
									}
								

								
									async Task DeleteForecast()
								
									{
								
									// Close the Popup
								
									ShowPopup =
									false;
								

								
									// Delete the forecast
								
									await Http
								
									.DeleteAsync("/api/WeatherForecast/Delete/" +
								
									Convert.ToInt32(objWeatherForecastDTO.Id));
								

								
									// Get the forecasts for the current user
								
									forecasts =
								
									await Http
								
									.GetJsonAsync<List<WeatherForecastDTO>>(
								
									"/api/WeatherForecast/GetAsync");
								
									}
								
}


Login and Logout On The Mobile Menu

image

Open the NavMenu.razor control and add the following code:


								
									<
									li
									class="nav-item px-3"
									>
								
									<
									AuthorizeView
									>
								
									<
									Authorized
									>
								
									<
									NavLink
									class="nav-link"
									href="AzureAD/Account/SignOut"
									>
								
									<
									span
									class="oi oi-account-logout"
									aria-hidden="true"
									>
									</
									span
									>
									[Log out]
								
									</
									NavLink
									>
								
									</
									Authorized
									>
								
									<
									NotAuthorized
									>
								
									<
									NavLink
									class="nav-link"
									href="AzureAD/Account/SignIn"
									>
								
									<
									span
									class="oi oi-account-login"
									aria-hidden="true"
									>
									</
									span
									>
									[Log in]
								
									</
									NavLink
									>
								
									</
									NotAuthorized
									>
								
									</
									AuthorizeView
									>
								
									</
									li
									>
								


image

This will add the Login (and Logout when logged in) links to the mobile menu.


Links

Blazor.net

EF Core Power Tools


Download

The project is available on the Downloads page on this site.

You must have Visual Studio 2019 Version 16.3 (or higher) installed to run the code.

An error has occurred. This application may no longer respond until reloaded. Reload 🗙