7/4/2022 Admin
Creating A Step-By-Step End-To-End Database Client-Side (WebAssembly) Blazor Application (Updated to .Net 6)
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
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.
You can log in.
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.
Use SQL Server
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 Blazor Application
Open Visual Studio.
Select Create a new Project.
Select Blazor WebAssembly App and click Next.
Name it EndToEnd and click Next.
Select .Net 6.
Select Individual Accounts under Authentication.
Select Configure for HTTPS and ASP.NET Core hosted.
Click Create.
The project will be created.
Create The Database
Open the SQL Server Object Explorer.
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.
You will specify just the server and Connect.
Expand the tree under the local SQL server, right-click on the Databases folder and select Add New Database.
Give the database a name and press Enter.
The database will be created.
Click on the tree node to expand the database (important because this causes the Properties to properly load)
Right-Click on the Database node and select Properties.
Open the Properties window if it is not already opened.
The Properties window for the database will display.
Copy the Connection string for the database.
Open the appsettings.json file in the Server project.
Paste in the connection string for the DefaultConnection and save the file.
Hit F5 to run the application.
The application will open in your web browser.
Click the Register link.
Enter the information to create a new account.
Click the Register button.
Because this is the first time the database is being used, you will see a message asking you to run the migration scripts that will create the database objects needed to support the user membership code.
Click the Apply Migrations button.
After the message changes to Migrations Applied, refresh the page in the web browser.
After clicking refresh in your web browser, a popup will require you to click Continue.
Click the Click here to confirm your account link.
On the Confirm email page, click the X to close the confirmation notice.
Click the name of the application to navigate to the home page.
Now you can click the Log in link to log in using the account you just created.
You will now be logged into the application.
You can click around the application and see that it works.
The Fetch data page currently shows random data. We will alter the application to allow us to add, update, and delete this data in the database.
Close the web browser to stop the application.
Create The WeatherForcast Table
Open the SQL Server Object Explorer.
Select the database.
Right-click on the Tables node and select Add New Table.
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));
The script will prepare.
Click the Update Database button.
Back in the Server Explorer window, right-click on Tables and select Refresh.
The WeatherForecast table will display.
Right-click on the table and select Show Table Data.
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 email of the user that we previously Registered with.
Create The Data Context
If you do not already have it installed, install EF Core Power Tools from:
https://marketplace.visualstudio.com/items?itemName=ErikEJ.EFCorePowerTools
(Note: Before installing, close Visual Studio)
(Note: Please give this project a 5 star review on marketplace.visualstudio.com!)
Right-click on the project node in the Solution Explorer and select EF Core Power Tools then Reverse Engineer.
Select EF Core 6.
Click the Add button.
Connect to the database.
Select the database connection in the dropdown and click OK.
Select the WeatherForecast table and click OK.
Set the values and click OK.
The Data Context will be created.
In the Solution Explorer, you will see the files for the Data Context.
Update the Program.cs File
Open the Program.cs file in the Server project.
Add the following code below the var builder = WebApplication.CreateBuilder(args); line:
// Read the connection string from the appsettings.json file
// Set the database connection for the EndtoEndContext
builder.Services.AddDbContext<EndToEndDB.Data.EndToEndContext>(options =>options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
To allow the user name to be readable in the Controller methods (to be created later), change the folliwng line:
builder.Services.AddIdentityServer().AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
To:
// This allows the username (email address) of the currrently
// logged in user to be read in the server side controller
builder.Services.AddIdentityServer().AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {options.IdentityResources["openid"].UserClaims.Add("name");options.ApiResources.Single().UserClaims.Add("name");
});
Create The Weather Forecast DTO
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.
Replace it with a file name WeatherForecastDTO.cs using the following code:
#nullable disableusing System;
using System.Collections.Generic;
using System.Text;
namespace EndToEnd.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
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:
#nullable disableusing System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using EndToEndDB.Data;
using Microsoft.EntityFrameworkCore;
using EndToEnd.Shared;
using EndToEndDB.Models;
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.Claims.Where(x => x.Type =="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
.FirstOrDefault().Value;// 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 voidPost([FromBody] WeatherForecastDTO paramWeatherForecast){// Create a new WeatherForecast instance
WeatherForecast objWeatherForecast =new WeatherForecast();
// Get the current user
string strCurrentUser = this.User.Claims.Where(x => x.Type =="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
.FirstOrDefault().Value;// 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 voidPut([FromBody] WeatherForecastDTO objWeatherForecast){// Get the current user
string strCurrentUser = this.User.Claims.Where(x => x.Type =="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
.FirstOrDefault().Value;// 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 voidDelete(int id)
{// Get the current user
string strCurrentUser = this.User.Claims.Where(x => x.Type =="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
.FirstOrDefault().Value;// 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
In the Client project, open the FetchData.razor page and replace all the code with the following code:
@page "/fetchdata"@using EndToEnd.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 != null)? 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 {#nullable disable// 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.GetFromJsonAsync<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.PostAsJsonAsync("/api/WeatherForecast/Post",
objNewWeatherForecastDTO);}else
{// This is an update
await Http.PutAsJsonAsync("/api/WeatherForecast/Put",
objWeatherForecastDTO);}// Get the forecasts for the current user
forecasts =await Http.GetFromJsonAsync<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.GetFromJsonAsync<List<WeatherForecastDTO>>("/api/WeatherForecast/GetAsync");
}}
Links
Implementing Roles In Blazor WebAssembly
Creating A Step-By-Step End-To-End Database Server-Side Blazor Application (updated to .Net 6)
Download
The project is available on the Downloads page on this site.
You must have Visual Studio 2022 (or higher) installed to run the code.