10/27/2024 Admin

Creating A Step-By-Step End-To-End Database Server-Side Blazor Application (updated to .Net 9)


The primary benefit we have when using server-side Blazor is that we do not have to make web http calls from the client code to the server code. This reduces the code we need to write and eliminates many security concerns.

In this article, 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.

 

Use SQL Server

image

The new project template in Visual Studio will allow you to create a database using SQL Server Express LocalDB. However, it can be problematic to install and configure. Using the free SQL Server 2022 Developer server (or the full SQL Server) is recommended.

Download and install SQL Server 2022 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 App and click Next.

 

image

Name it EndToEnd and click Next.

 

image

Select .Net 9.

Select Individual Accounts under Authentication.

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

Right-Click on the Database node and select Refresh (important because this causes the Properties to properly load).

 

image

Right-Click on the Database node again 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.

 

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

The Confirm email page will display the confirmation notice.

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 Weather 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.

 

Do Not Require Account Confirmation

image

If we do not intend to configure email account verification (using the directions at this link), we can open the Program.cs file and change the following line:

 

            builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddSignInManager()
                .AddDefaultTokenProviders();

 

To:

 

            builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddSignInManager()
                .AddDefaultTokenProviders();

 

Create The Database

image

In the SQL Server Object Explorer window, in Visual Studio, we see the tables that the migration scripts added.

 

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.

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 registered an account for earlier.

 

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: 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

Select Add Database Connection.

 

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

In the Solution Explorer, you will see the Data Context has been created.

 

image

Open the Program.cs file.

Add the following code above the var app = builder.Build(); line:

 

            #if DEBUG
            // Logging data with EnableSensitiveDataLogging is a security risk, as it may expose passwords 
            // and other Personally Identifiable Information (PII) when it logs SQL statements executed 
            // against the database. Only enable EnableSensitiveDataLogging for development and testing
            builder.Services.AddDbContextFactory<EndToEndContext>(options =>
                            options.UseSqlServer(connectionString)
                            .EnableSensitiveDataLogging());
            #else
            builder.Services.AddDbContextFactory<EndToEndContext>(options =>
                            options.UseSqlServer(connectionString));
            #endif
 

 

image

Save the file.

Select Build, then Rebuild Solution.

 

Read From The Database

We want to replace the current Weather.razor page with a page that reads and writes to the database.

The first step is to add support for the QuickGrid component that will display the data.

 

image

Install the following NuGet packages:

Microsoft.AspNetCore.Components.QuickGrid

Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter

 

image

Open the Program.cs file.

Add the following code above the var app = builder.Build(); line:

 

            builder.Services.AddQuickGridEntityFrameworkAdapter();
            builder.Services.AddDatabaseDeveloperPageExceptionFilter();

 

image

Open the Weather.razor file.

Replace all the code with the following code:

 

@page "/weather"
@attribute [Authorize]
@using EndToEnd.Components.Account
@using EndToEnd.Data
@using Microsoft.EntityFrameworkCore
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.QuickGrid
@using EndToEnd.Models
@using System.Security.Claims
@implements IAsyncDisposable
@inject IDbContextFactory<EndToEndContext> DbFactory
@inject AuthenticationStateProvider AuthenticationStateProvider

 

Primarily this injects the EndToEndContext that connects to the database and the AuthenticationStateProvider that allows access to the currently authenticated user.

 

Note we add the [Authorize] tag to restrict access to this page to authenticated users.

If a user is not authenticated, they will automatically be redirected to the login page.

 

Also note that this page implements IAsyncDisposable.

This requires us to implement a DisposeAsync method in the @code block that will dispose of the EndToEndContext, that connects to the database, when the user navigates away from the page:

 

@code {
#nullable disable
    private EndToEndContext context = default!;
   
    public async ValueTask DisposeAsync()
    {
        // Dispose the context
        await context.DisposeAsync();
    }
}

 

Next add the following markup to display the header and the name of the current user:

 

<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>Current user: @CurrentUser</p>

 

Also add the markup for the QuickGrid:

 

<QuickGrid @ref="myGrid" Class="table table-striped" Items="WeatherForecasts">
    <PropertyColumn Property="weatherforecast => weatherforecast.Date" Sortable="true" />
    <PropertyColumn Property="weatherforecast => weatherforecast.TemperatureC" Sortable="true" />
    <PropertyColumn Property="weatherforecast => weatherforecast.TemperatureF" Sortable="true" />
    <PropertyColumn Property="weatherforecast => weatherforecast.Summary" Sortable="true" />
    <PropertyColumn Property="weatherforecast => weatherforecast.UserName" />
</QuickGrid>

 

Next, add the following fields to the @code section:

 

    private IQueryable<WeatherForecast> WeatherForecasts;
    private WeatherForecast selectedWeatherForecast = new();
    private QuickGrid<WeatherForecast> myGrid;
    private string CurrentUser = "";

 

Finally, add the following methods:

 

    protected override void OnInitialized()
    {
        // Create a new database context
        context = DbFactory.CreateDbContext();
    }
    protected override async Task OnInitializedAsync()
    {
        // Get the current user
        var authState =
        await AuthenticationStateProvider.GetAuthenticationStateAsync();
        if (authState.User.Identity is not null && authState.User.Identity.IsAuthenticated)
        {
            // Set the current user
            CurrentUser = authState.User.Identity.Name;
            // Load weather forecasts for the current user
            WeatherForecasts = context.WeatherForecasts.Where(w => w.UserName == CurrentUser);
        }
    }

 

image

Build and run the project.

If we are not logged in, and we go to the Weather page, we will be directed to the Login page.

Log in as the user we created data for earlier.

 

image

After you are logged in, switch to the Weather page and you will see the data for the user we entered earlier.

Stop the project and return to Visual Studio.

 

Inserting Data Into The Database

Open the Weather.razor file and add the following fields:

 

    private bool isPopupVisible, isDeletePopupVisible = false;
    private string modalTitle = "Create New Weather Forecast";
    private string modalButtonText = "Save";
    private bool isEditMode = false;

 

Add the following HTML markup, to implement a Create New button, above the existing table element:

 

<p>
    <button class="btn btn-primary" @onclick="() => CreateNew()">Create New</button>
</p>

 

Add the following code below the table code to create a popup to allow a new record to be created (and later edited):

 

<!-- Reusable Modal -->
@if (isPopupVisible)
{
    <div class="modal fade show" style="display: block;" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">@modalTitle</h5>
                    <button type="button" class="btn-close" @onclick="ClosePopup"></button>
                </div>
                <div class="modal-body">
                    <EditForm Model="selectedWeatherForecast" OnValidSubmit="HandleFormSubmit"
                              class="needs-validation" novalidate>
                        <DataAnnotationsValidator />
                        <ValidationSummary />
                        <div class="mb-3">
                            <label for="date" class="form-label">Date</label>
                            <InputDate @bind-Value="selectedWeatherForecast.Date"
                                       class="form-control" id="date" />
                        </div>
                        <div class="mb-3">
                            <label for="tempC" class="form-label">Temperature (C)</label>
                            <InputNumber @bind-Value="selectedWeatherForecast.TemperatureC"
                                         class="form-control" id="tempC" />
                        </div>
                        <div class="mb-3">
                            <label for="tempF" class="form-label">Temperature (F)</label>
                            <InputNumber @bind-Value="selectedWeatherForecast.TemperatureF"
                                         class="form-control" id="tempF" />
                        </div>
                        <div class="mb-3">
                            <label for="summary" class="form-label">Summary</label>
                            <InputText @bind-Value="selectedWeatherForecast.Summary"
                                       class="form-control" id="summary" />
                        </div>
                        <button type="submit" class="btn btn-primary">@modalButtonText</button>
                    </EditForm>
                </div>
            </div>
        </div>
    </div>
    <div class="modal-backdrop fade show"></div>
}

 

This adds a form (that will be displayed as popup because the class for the DIV is modal), that allows the user to enter (and later edit) data for a forecast.

We do not need JavaScript to make this popup show. We only need to wrap this code with:

 

            @if (isPopupVisible)
            {
                ...
            }

 

When the isPopupVisible value is true the popup will show. When the value is false, the popup will disappear.

Add the following code to the @code section:

 

    private void CreateNew()
    {
        // Create a new weather forecast
        selectedWeatherForecast = new WeatherForecast();
        selectedWeatherForecast.Date = DateTime.Now;
        selectedWeatherForecast.UserName = CurrentUser;
        // Show the popup
        ShowPopup(selectedWeatherForecast, isEdit: false);
    }
    private void ShowPopup(WeatherForecast weatherForecast, bool isEdit)
    {
        // Set the selected weather forecast
        selectedWeatherForecast = weatherForecast;
        isEditMode = isEdit;
        modalTitle = isEdit ? "Edit Weather Forecast" : "Create New Weather Forecast";
        modalButtonText = isEdit ? "Save Changes" : "Save";
        // Show the popup
        isPopupVisible = true;
    }
    private void ClosePopup()
    {
        // Close the popup
        isPopupVisible = false;
    }
    private async Task HandleFormSubmit()
    {
        if (isEditMode)
        {
            // Update the selected weather forecast
            context.WeatherForecasts.Update(selectedWeatherForecast);
        }
        else
        {
            // Add the selected weather forecast
            context.WeatherForecasts.Add(selectedWeatherForecast);
        }
        // Save changes
        await context.SaveChangesAsync();
        await myGrid.RefreshDataAsync();
        // Close the popup
        ClosePopup();
    }

 

image

When you run the project, you can click the Create New button to add an entry.

 

image

The form only requires a Date, Fahrenheit, Celsius, and a summary, because the username, will be set by the code.

 

image

After clicking the Save button, the entry is saved to the database and displayed.

 

Updating The Data

Add the following code to the existing table element that adds an edit button in the last column (that calls the ShowPopup method created earlier):

 

    <TemplateColumn Context="weatherforecast">
        <button @onclick="() => ShowPopup(weatherforecast, isEdit: true)"
                class="btn btn-link p-0 m-0 border-0 text-decoration-underline">
            Edit
        </button> 
    </TemplateColumn>

 

This sets the current record to the objWeatherForecast object that the popup is bound to, and opens the popup.

 

image

When we run the application, we now have an Edit button to edit the existing record.

 

image

The existing record will display in the popup, allowing us to edit the data and save it.

 

image

The updated record is then displayed in the table.

 

Deleting The Data

Add code markup for a Delete button to the TemplateColumn tag:

 

        | <button @onclick="() => ShowDeletePopup(weatherforecast)" 
            class="btn btn-link p-0 m-0 border-0 text-decoration-underline text-danger">Delete</button>

 

Add the following markup code for a delete confirmation popup:

 

<!-- Delete Confirmation Modal -->
@if (isDeletePopupVisible)
{
    <div class="modal fade show" style="display: block;" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Delete Weather Forecast</h5>
                    <button type="button" class="btn-close"
                            @onclick="CloseDeletePopup"></button>
                </div>
                <div class="modal-body">
                    <p>Are you sure you want to delete this forecast?</p>
                    <button class="btn btn-danger"
                            @onclick="HandleDelete">
                        Delete
                    </button>
                    <button class="btn btn-secondary"
                            @onclick="CloseDeletePopup">
                        Cancel
                    </button>
                </div>
            </div>
        </div>
    </div>
    <div class="modal-backdrop fade show"></div>
}

 

Add the following code to the @code section:

 

    private void ShowDeletePopup(WeatherForecast weatherForecast)
    {
        // Set the selected weather forecast
        selectedWeatherForecast = weatherForecast;
        // Show the delete popup
        isDeletePopupVisible = true;
    }
    private void CloseDeletePopup()
    {
        // Close the delete popup
        isDeletePopupVisible = false;
    }
    private async Task HandleDelete()
    {
        // Remove the selected weather forecast
        context.WeatherForecasts.Remove(selectedWeatherForecast);
        await context.SaveChangesAsync();
        await myGrid.RefreshDataAsync();
        // Close the delete popup
        CloseDeletePopup();
    }

 

image

When we run the code and click the Delete button next to a record…

 

image

It opens a delete confirmation popup.

We now see a Delete button that will delete the record.

 

 

Links

Blazor.net

ASP.NET Core Blazor authentication and authorization

EF Core Power Tools

QuickGrid / QuickGrid samples

 

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 error has occurred. This application may no longer respond until reloaded. Reload 🗙