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

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.

 

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.

 

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 Blazor Application

image

Open Visual Studio.

 

image

Select Create a new Project.

 

image

Select Blazor WebAssembly App and click Next.

 

image

Name it EndToEnd and click Next.

 

image

Select .Net 6.

Select Individual Accounts under Authentication.

Select Configure for HTTPS and ASP.NET Core hosted.

Click Create.

 

image

The project will be created.

 

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

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.

 

image

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.

 

image

Open the appsettings.json file in the Server project.

 

image

Paste in the connection string for the DefaultConnection and save the file.

 

image

Hit F5 to run the application.

 

image

The application will open in your web browser.

Click the Register link.

 

image

Enter the information to create a new account.

Click the Register button.

 

image

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.

 

image

After the message changes to Migrations Applied, refresh the page in the web browser.

 

image

After clicking refresh in your web browser, a popup will require you to click Continue.

 

image

Click the Click here to confirm your account link.

 

image

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.

 

image

Now you can click the Log in link to log in using the account you just created.

 

image

You will now be logged into the application.

You can click around the application and see that it works.

 

image

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

 

image

Open the SQL Server Object Explorer.

Select the database.

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 email of the user that we previously Registered with.

 

 

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

(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

Select EF Core 6.

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.

The Data Context will be created.

 

image

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

 

Update the Program.cs File

image

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

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:

 

#nullable disable
using 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

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:

 

#nullable disable
using 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 void
            Post([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 void
               Put([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 void
            Delete(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

image

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)

Blazor.net

EF Core Power Tools

 

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.

An unhandled error has occurred. Reload 🗙