Blazor forms and validation

Blazor forms and validation

In this tutorial I will cover how to insert data from forms in Blazor. I will also explain how to perform data validations in forms so that the data should be in proper format before being inserted into the database. First, I will create a database in MSSQLLocalDB then perform data Insertion, Updation, Deletion and Reading in Entity Framework Core.

Preparing the project and database

This tutorial uses a project that contains the necessary Blazor configurations. I have explained this in my tutorial called First Application in Blazor from scratch. You can visit it for learning the work of various files in Blazor.

I have named my project as BlazorForms, you can download the project from the download link given at the end of this tutorial.

Install Entity Framework Core

In your project install the following 3 packages from NuGet:

  • 1. Microsoft.EntityFrameworkCore.SqlServer
  • 2. Microsoft.EntityFrameworkCore.Tools
  • 3. Microsoft.EntityFrameworkCore.Design
I have written a complete tutorial on Installation of Entity Framework Core which you can look for the installation procedure in details.

Adding Model Classes

There will be 3 entities which are described in 3 classes – Student.cs, School.cs and Location.cs. These will be for Students information, School information and Location of Students.

So, first of all create a new folder called Models inside the root of the project and inside it adds the following 3 classes.

1. Student.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorForms.Models
{
    public class Student
    {
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        public string Name { get; set; }

        [Range(8, 15)]
        public int Age { get; set; }

        public DateTime DOB { get; set; }

        [Range(5, 10)]
        public int Standard { get; set; }

        [Required]
        public string Sex { get; set; }

        [RegularExpression("^[a-zA-Z0-9_\\.-][email protected]([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$", ErrorMessage = "E-mail is not valid")]
        public string Email { get; set; }

        [Range(typeof(bool), "true", "true", ErrorMessage = "You must accept the Terms")]
        public bool Terms { get; set; }

	 [Range(1, int.MaxValue, ErrorMessage = "Please Select School")]
        public int SchoolId { get; set; }
        public School School_R { get; set; }

	 [Range(1, int.MaxValue, ErrorMessage = "Please Select Location")]
        public int LocationId { get; set; }
        public Location Location_R { get; set; }
    }
}
2. School.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorForms.Models
{
    public class School
    {
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        public string Name { get; set; }

        public IEnumerable<Student> Student_R{ get; set; }
    }
}
3. Location.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorForms.Models
{
    public class Location
    {
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        public string City { get; set; }

        [Required]
        [StringLength(50)]
        public string State { get; set; }

        public IEnumerable<Student> Student_R { get; set; }
    }
}

The attributes applied on the fields like [Required], [StringLength], [Range] etc are Data Annotations, and they will be used for doing Validations. Refer – Model Validation from Data Annotations.

These attributes will also configure Entity Framework Core Entities. For example, by applying [Required] attribute, the respective field’s Allow Nulls property in the database will be un-checked.

In the same way the [StringLength(50)] will turn the max size of column to 50. It is applied to the “Name” fields of the Student and School classes which makes the “Name” column in the database as nvarchar(50).

I have written a separate tutorial that covers this topic – Configurations in Entity Framework Core.

Data Context

Next add a new class called DataContext.cs inside the Models folder. I will work as the Data Content class of Entity Framework Core. The full code of this class is given below:

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorForms.Models
{
    public class DataContext : DbContext
    {
        public DataContext(DbContextOptions<DataContext> options) : base(options) { }
        public DbSet<Student> Student { get; set; }
        public DbSet<School> School { get; set; }
        public DbSet<Location> Location { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Student & Location One to Many
            modelBuilder.Entity<Student>()
                    .HasOne(e => e.Location_R)
                    .WithMany(e => e.Student_R)
                    .HasForeignKey(e => e.LocationId)
                    .OnDelete(DeleteBehavior.Cascade);

            // Student & School One to Many
            modelBuilder.Entity<Student>()
                    .HasOne(e => e.School_R)
                    .WithMany(e => e.Student_R)
                    .HasForeignKey(e => e.SchoolId)
                    .OnDelete(DeleteBehavior.Cascade);
        }
    }
}

Inside the OnModelCreating method, I have added the One-to-Many Relationship between Student entity and School entity. Which means One School and have multiple Students in it.

Similarly, there is One-to-Many Relationship between the Student entity and Location entity.

Consider reading Configure One-to-Many relationship using Fluent API in Entity Framework Core if you have any doubts about how these relationships are made in Entity Framework Core.

Startup.cs

In the Startup.cs I have registered the Data Content file as a service and specified the connection string of the database. See the below code where I have highlighted the necessary codes:

using BlazorForms.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorForms
{
    public class Startup
    {
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<DataContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddRazorPages();
            services.AddServerSideBlazor();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
        }
    }
}

appsettings.json

I have added the connection in the appsettings.json file which is located on the root of the project. See it’s highlighted code below:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=SchoolDB;MultipleActiveResultSets=True"
  }
}

The database is selected as MSSQLLocalDB and name of the database is taken as SchoolDB.

_Imports.razor

I will need to import the necessary namespaces of the Entity Framework Core. So, I have added them to the _Imports.razor file. See it’s code below:

@using Microsoft.AspNetCore.Components.Routing
@using BlazorForms.Shared
@using Microsoft.AspNetCore.Components.Web
@using BlazorForms.Models
@using Microsoft.EntityFrameworkCore;
@using System.Linq;
@using Microsoft.AspNetCore.Components.Forms;

Performing Migrations

Now coming to the final step which is to perform migrations. This will create the Database. Run the following 2 commands one after the other in the Package Manager Console window.

dotnet ef migrations add Migration1
dotnet ef database update

When the commands finished the database will be created. You can check the database in the SQL Server Object Explorer window.

I have shown the full working of this step in the below video:

Entity Framework Core migrations

Inserting Data in Entity Framework Core

In order to use Entity Framework Core in Blazor you will need to Inject the Data Context to the Razor Component by using the [Inject] attribute as shown below:

[Inject]
public DataContext Context { get; set; }

The Data Context file resides in the Models folder so I have imported it’s namespace in the _Imports.razor component.

@using BlazorForms.Models

So, let us create the Insert data feature to the School Table of the database. First create a new Razor Component called ManageSchool.razor inside the “Pages” folder of your app.

It will mainly have a text box for entering School Name and a button which will insert this school to the School table of the database. Add the below code to this component.

@page "/ManageSchool"

<h1 class="bg-info text-white">Manage School</h1>

<h2 class="text-success bg-light p-2">Add a School</h2>

<h3 class="text-warning bg-light p-2">@FormSubmitMessage</h3>

<div class="form-group">
    <label>Name:</label>
    <input class="form-control" type="text" @bind="SchoolData.Name" />
    <button class="m-1 btn btn-primary" @onclick="Create">Click</button>
</div>
@code {
    [Inject]
    public DataContext Context { get; set; }

    public School SchoolData = new School();

    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";

    public void Create()
    {
        Context.Add(SchoolData);
        Context.SaveChanges();
        
        FormSubmitMessage = "Form Data Submitted";
   	    SchoolData = new School();
    }
}

I have defined a C# School type object called SchoolData and my text box is bound to it’s Name property using the @bind attribute as shown below:

<input class="form-control" type="text" @bind="SchoolData.Name" />

This is the basics of binding a html element to a class type object, and it will be used throughout this tutorial. If there were more properties defined in the School class then they would be bound in the same way to different HTML elements.

Next, in the button click event I am inserting the new School to the School table of the database. Note that the Id property of the SchoolData object is set to 0 since is the default value of an Int type.

Context.Add(SchoolData);
Context.SaveChanges();

I have also updated the FormSubmitMessage variable value to “Form Data Submitted” so that users can know the outcome of the insert operation.

FormSubmitMessage = "Form Data Submitted";

It’s value is shown inside the h3 tag:

Notice I have also reinitialized the SchoolData object inside the Create method:

SchoolData = new School();

This is done so that new School can be inserted the next time. This is needed since Blazor maintains the state of variable and it becomes necessary to reinitialize them in this case.

<h3 class="text-warning bg-light p-2">@FormSubmitMessage</h3>

Now run your app and go to the URL – https://localhost:44366/ManageSchool and try inserting a new school.

It will work perfectly as shown in the below video.

Now I will add a new feature to the same Razor Component. This feature will show all the School records from the School table. There will also be a delete button for deleting a school record.

So, add an HTML table and the binding code which is emphasize in changed colour, see below.

@page "/ManageSchool"

<h1 class="bg-info text-white">Manage School</h1>

<h2 class="text-success bg-light p-2">Add a School</h2>

<h3 class="text-warning bg-light p-2">@FormSubmitMessage</h3>

<div class="form-group">
    <label>Name:</label>
    <input class="form-control" type="text" @bind="SchoolData.Name" />
    <button class="m-1 btn btn-primary" @onclick="Create">Click</button>
</div>

<table class="table table-sm table-bordered table-striped ">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Delete</th>
        </tr>
    </thead>
    <tbody>
        @foreach (School s in Schools)
        {
            <tr>
                <td>@s.Id</td>
                <td>@s.Name</td>
                <td>
                    <button class="btn btn-sm btn-danger"
                            @onclick="@(() => Delete(s))">
                        Delete
                    </button>
                </td>
            </tr>
        }
    </tbody>
</table>

@code {
    [Inject]
    public DataContext Context { get; set; }

    public School SchoolData = new School();

    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";

    public void Create()
    {
        Context.Add(SchoolData);
        Context.SaveChanges();
        FormSubmitMessage = "Form Data Submitted";

        SchoolData = new School();
        UpdateSchools();
    }

    public List<School> Schools = new List<School>();
    protected override void OnInitialized()
    {
        UpdateSchools();
    }

    public void UpdateSchools()
    {
        Schools = Context.School.ToList();
    }

    public void Delete(School s)
    {
        Context.Remove(s);
        Context.SaveChanges();
        UpdateSchools();
    }
}

Explanation : I have defined a List<School> type variable called School which will contain all the Schools record. Whenever the component is initialized the UpdateSchools() method is called, which will fetch all the Schools from the database with Entity Framework Core.

protected override void OnInitialized()
{
    UpdateSchools();
}

public void UpdateSchools()
{
    Schools = Context.School.ToList();
}
You can learn more about OnInitialized lifecycle method in my tutorial called Razor Components Lifecycle Methods of Blazor.

The HTML form shows all the Schools by looping through them and showing each of them inside a tr element.

@foreach (School s in Schools)
{
    …
}

The last column of each row of Schools contains a button whose work is to delete the respective row of School.

<td>
    <button class="btn btn-sm btn-danger"
            @onclick="@(() => Delete(s))">
        Delete
    </button>
</td>

public void Delete(School s)
{
    Context.Remove(s);
    Context.SaveChanges();
    UpdateSchools();
}

You can now run and check all the functionality which I have shown in the below video.

Although this works but there are lots of shortcoming in this approach. These are:

  • 1. There is no Data Validations.
  • 2. No error messages are displayed.

Thankfully Blazor provides built-in Form Components which you can use in your app and create professional forms having validations support. Let us understand them in the next section.

Form components in Blazor

Blazor Provides Built-in Form Component that are used to receive and validate the user input. These inputs are validated when they are changed and also when the form is submitted. These components resides in the Microsoft.AspNetCore.Components.Forms namespace. In the below table I have listed all of them.

Component Description
EditForm It renders a form element that also performs data validations.
InputText It renders an input element of type text. It can be bind to a C# value.
InputNumber It renders an input element of type number. It can be bind to a C# int, long, float, double, or decimal values. Here “T” is the type.
InputFile It renders an input element of type file.
InputDate It renders an input element of type date. It can be bind to a C# DateTime or DateTimeOffset values.
InputCheckbox It renders an input element of type checkbox and that is bound to a C# bool property.
InputSelect It renders a html select element. T is the type
InputRadio It renders a input type radio element.
InputRadioGroup Use InputRadio components with the InputRadioGroup component to create a radio button group.
InputTextArea It renders a html select element.

Now I will use these Form Components provided by Blazor to create CRUD features for Location entity.

So, create a new Razor Component called ManageLocation.razor inside the Pages folder and add to it the following code:

@page "/ManageLocation"

<h1 class="bg-info text-white">Manage Location</h1>
<h2 class="text-success p-2">@FormSubmitMessage</h2>

<EditForm Model="LocationData" OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div class="form-group">
        <label>City</label>
        <InputText class="form-control" @bind-Value="LocationData.City" />
    </div>
    <div class="form-group">
        <label>State</label>
        <InputText class="form-control" @bind-Value="LocationData.State" />
    </div>
    <div class="text-center">
        <button type="submit" class="btn btn-primary">Click</button>
    </div>
</EditForm>

@code {
    [Inject]
    public DataContext Context { get; set; }

    public Location LocationData { get; set; } = new Location();

    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";

    public void HandleValidSubmit()
    {
        Context.Add(LocationData);
        Context.SaveChanges();
        FormSubmitMessage = "Form Data Submitted";
    }

    public void HandleInvalidSubmit() => FormSubmitMessage = "Invalid Data Submitted";
}

Here I have used the EditForm component of Blazor. The 4 important attributes of the EditForm component are:

  • Model – is used to provide the EditForm component with the object that the form is used to edit and validate.
  • OnValidSubmit – is used to call an event which is triggered when the form is submitted and the form data passes validation.
  • OnInvalidSubmit – is used to call an event which is triggered when the form is submitted and the form data fails validation.
  • OnSubmit – is used to call an event which is triggered when the form is submitted and before validations are performed.

These Blazor Events are triggered by adding a conventional submit button.

Notice in the above code I have used the EditForm component to render a form. I have applied the 3 attributes – Model, OnValidSubmit and OnInvalidSubmit to the EditForm component.

<EditForm Model="LocationData" OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">

The Model attribute specifies the Model object for the form which is LocationData variable.

public Location LocationData = new Location();

The LocationData properties called “City” and “State” are bound to the two InputText components using the bind-Value attribute.

<InputText class="form-control" @bind-Value="LocationData.City" />
<InputText class="form-control" @bind-Value="LocationData.State" />

There is a simple submit button which will submit this form.

<button type="submit" class="btn btn-primary">Click</button>

The OnValidSubmit attribute specifies the event called when the form is submitted and the form data passes validation. This is the ideal place to insert the record to the database. So, I have called a hander HandleValidSubmit where the location data is inserted.

public void HandleValidSubmit()
{
    Context.Add(LocationData);
    Context.SaveChanges();
    FormSubmitMessage = "Form Data Submitted";

    LocationData = new Location();
}

The OnInvalidSubmit attribute specifies the event called when the form is submitted but the form data do not pass validations. I have applied a hander method called “HandleInvalidSubmit” for this event, and which changes the value of the FormSubmitMessage property to “Invalid Data Submitted”.

public void HandleInvalidSubmit() => FormSubmitMessage = "Invalid Data Submitted";

The value of this property is shown inside a h2 tag:

<h2 class="text-success p-2">@FormSubmitMessage</h2>

I have also added 2 Blazor Validation Components inside the EditForm component and these are:

<DataAnnotationsValidator />
<ValidationSummary />
I have covered some very advanced features of Razor Components which you will like to learn – Advanced Razor Components features in Blazor

Blazor Validation Components

Blazor provides 3 Validation components to perform data validations, and show Validation messages. These are:

Component Description
DataAnnotationsValidator This component integrates the validation attributes applied to Model Class (Data Annotations) into the Blazor Validation system.
ValidationMessage It displays validation error messages for a single property.
ValidationSummary It displays validation error messages for the entire form.

You can now run the project and go to the URL – https://localhost:44366/ManageLocation, just click the button without entering anything on the text boxes. You will see message – ‘Invalid Data Submitted’ along with 2 validation error messages:

  • The City field is required.
  • The State field is required.

I have shown these messages in the below image:

validation error messages blazor

The ValidationSummary component is rendered as a ul elements containing li elements and validation messages are shown inside the li elements. On checking the page source, you will find the generated HTML as given below.

  • The City field is required.
  • The State field is required.

Notice the CSS Classes validation-errors and validation-message that are assigned to the generated html elements where validation messages are shown. I have described them in the below table:

Component Description
validation-errors The ValidationSummary component generates a top level ‘ul’ container for the summary of validation messages and assigns validation-errors CSS class to it.
validation-message The top level ‘ul’ container is populated with ‘li’ elements for each validation message. These li elements are assigned with validation-message CSS class.

I will now style these CSS class to show the validation errors in a better way. So create a new stylesheet called validation.css inside the wwwroot folder of your project and add the following code to it:

.validation-errors {
    background-color: #ff6a00;
    padding: 8px;
    font-size: 16px;
    font-weight: 500;
}

li.validation-message {
    color: #FFFFFF;
    font-weight: 500
}

Here I have applied the styling for the classes added by the ValidationSummary component. It typically makes the red background with white error messages.

Now add the reference of this style to the ManageLocation.razor component by adding it’s link at the top.

<link href="/validation.css" rel="stylesheet" />

Run the app and check how it look. Now the validation messages are shows like below.

ValidationSummary custom styling

ValidationMessage – Showing Validation Messages for individual properties

I can also display validation messages for a specific field by using ValidationMessage component. Update the code of the component to contain 2 ValidationMessage as shown in highlighted way:

@page "/ManageLocation"
<link href="/validation.css" rel="stylesheet" />

<h1 class="bg-info text-white">Manage Location</h1>
<h2 class="text-success p-2">@FormSubmitMessage</h2>

<EditForm Model="LocationData" OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div class="form-group">
        <label>City</label>
        <ValidationMessage For="@(() => LocationData.City)" />
        <InputText class="form-control" @bind-Value="LocationData.City" />
    </div>
    <div class="form-group">
        <label>State</label>
        <ValidationMessage For="@(() => LocationData.State)" />
        <InputText class="form-control" @bind-Value="LocationData.State" />
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Click</button>
    </div>
</EditForm> 
@code {
    [Inject]
    public DataContext Context { get; set; }

    public Location LocationData = new Location();

    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";

    public void HandleValidSubmit()
    {
        Context.Add(LocationData);
        Context.SaveChanges();
        FormSubmitMessage = "Form Data Submitted";

        LocationData = new Location();
    } 
        public void HandleInvalidSubmit() => FormSubmitMessage = "Invalid Data Submitted";
}

I have added 2 ValidationMessage components to show individual validation messages for City and State fields. See below.

<ValidationMessage For="@(() => LocationData.City)" /> 
<ValidationMessage For="@(() => LocationData.State)" /> 

The ValidationMessage components are rendered as a div element containing “validation-message” CSS class.

Blazor Validation System also adds individual CSS Classes to form elements when they are edited by the user, when they pass validations or on failing validations. I have described these CSS classes on the below table.

Component Description
modified This CSS is added to the element when the user has edited it’s value.
valid This CSS is added when the element passes valdiation.
invalid This CSS is added when the element fails valdiation.

I will also system these CSS classes in my stylesheet to increase the feel and look of the form. So add the following code to the validation.css file.

.validation-errors {
    background-color: #ff6a00;
    padding: 8px;
    font-size: 16px;
    font-weight: 500;
}

li.validation-message {
    color: #FFFFFF;
    font-weight: 500
}

div.validation-message {
    color: #ff6a00;
    font-weight: 500
}

modified.valid {
    border: solid 3px #ffd800;
}

.invalid {
    border: solid 3px #ff6a00;
}

Now run and check the functionality. The validation feature works perfectly and I have shown this in the below video.

blazor validation video
ErrorMessage property

I get the errors messages as:

  • The City field is required.
  • The State field is required.

I can change their text using the ErrorMessage property that can be applied to the DataAnnotations attributes. In the below code I have applied it to the Location class.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace BlazorForms.Models
{
    public class Location
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Please add city")]
        [StringLength(50)]
        public string City { get; set; }

        [Required(ErrorMessage = "Please add state")]
        [StringLength(50)]
        public string State { get; set; }

        public IEnumerable<Student> Student_R { get; set; }
    }
}

Now if you check the validation messages becomes:

  • Please add city
  • Please add state

Reading of Location Records

In the above section I built the insert feature for Location. I will now create the feature for Reading of Location record in the same razor component.

I will first need to create 2 column design for the razor component. In the left column I will show the Create Location form while on the right column I will show all the Location Records. I will use Bootstrap to create this design. Below I have shown the updated code of the ManageLocation.razor component.

This includes addition of new divs containing css classes “container”, “row”, “col-sm-12”, “col-sm-5”, “col-sm-7”, which are bootstrap classes to create a 2-column design. There is also an HTML form element which will show all the location records. Notice the use of foreach loop to create tr elements for each row of record.

@page "/ManageLocation/{id:int}"
@page "/ManageLocation"

<link href="/validation.css" rel="stylesheet" />

<div class="container">
    <div class="row">
        <div class="col-sm-12">
            <h1 class="bg-info text-white">Manage Location</h1>
        </div>
        <div class="col-sm-5">
            <h2 class="text-success p-2">@FormSubmitMessage</h2>
            <EditForm Model="LocationData" OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
                <DataAnnotationsValidator />
                <ValidationSummary />

                <div class="form-group">
                    <label>City</label>
                    <ValidationMessage For="@(() => LocationData.City)" />
                    <InputText class="form-control" @bind-Value="LocationData.City" />
                </div>
                <div class="form-group">
                    <label>State</label>
                    <ValidationMessage For="@(() => LocationData.State)" />
                    <InputText class="form-control" @bind-Value="LocationData.State" />
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">Click</button>
                </div>
            </EditForm>
        </div>
        <div class="col-sm-7">
            <table class="table table-sm table-bordered table-striped ">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>City</th>
                        <th>State</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (Location a in Locations)
                    {
                        <tr>
                            <td>@a.Id</td>
                            <td>@a.City</td>
                            <td>@a.State</td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    </div>
</div>  
@code {
    [Inject]
    public DataContext Context { get; set; }

    public Location LocationData = new Location();

    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";

    public void HandleValidSubmit()
    {
        if (Id == 0)
            Context.Add(LocationData);
        Context.SaveChanges();

        UpdateBindings(0, "/ManageLocation", "Form Data Submitted");
        UpdateLocations();
    }

    public void HandleInvalidSubmit() => FormSubmitMessage = "Invalid Data Submitted";

    public List<Location> Locations = new List<Location>();

    protected override void OnInitialized()
    {
        UpdateLocations();
    }

    public void UpdateLocations()
    {
        if (Id != 0)
            LocationData = Context.Location.Where(a => a.Id == Id).FirstOrDefault();
        Locations = Context.Location.ToList();
    }

    [Parameter]
    public int Id { get; set; }

    [Inject]
    public NavigationManager NavManager { get; set; }

    public void UpdateBindings(int idValue, string NavigationValue, string FormSubmitValue)
    {
        Id = idValue;
        NavManager.NavigateTo(NavigationValue);
        FormSubmitMessage = FormSubmitValue;
        LocationData = new Location();
    }
}

There are also some additions to the C# code which are shown in heightened manner.

1. The UpdateLocation() method task is to fetch Location records from the database. It fills 2 variables with the data –

a. LocationData with a single record whose Id is provided to the route variable ‘Id’.

b.Locations will all the location records.

2. The UpdateBindings() method task is to update the URL of the razor component. So that if the Id of the record is send to the route then URL will become https://localhost:44366/ManageLocation/1, https://localhost:44366/ManageLocation/2 and so on.

It also updated the value of Id property based on the URL. Changes the value of FormSubmitMessage property and also initializes the ‘LocationData’ object.

This thing will come out to be very handy when creating the Edit and Delete feature.

3. Added the OnInitialized() method which fetches all the Location records from the database.

Now run and see how it works. I have shown this in the below video:

Editing of Location Records

For doing editing, you will need to add a new column called Edit to the HTML table. In this column add a button and handle it’s click event. The necessary code to be added is shown in highlighted way.

@page "/ManageLocation/{id:int}"
@page "/ManageLocation"

<link href="/validation.css" rel="stylesheet" />

<div class="container">
    <div class="row">
        <div class="col-sm-12">
            <h1 class="bg-info text-white">Manage Location</h1>
        </div>
        <div class="col-sm-5">
            <h2 class="text-success p-2">@FormSubmitMessage</h2>
            <EditForm Model="LocationData" OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
                <DataAnnotationsValidator />
                <ValidationSummary />

                <div class="form-group">
                    <label>City</label>
                    <ValidationMessage For="@(() => LocationData.City)" />
                    <InputText class="form-control" @bind-Value="LocationData.City" />
                </div>
                <div class="form-group">
                    <label>State</label>
                    <ValidationMessage For="@(() => LocationData.State)" />
                    <InputText class="form-control" @bind-Value="LocationData.State" />
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">Click</button>
                </div>
            </EditForm>
        </div>
        <div class="col-sm-7">
            <table class="table table-sm table-bordered table-striped ">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>City</th>
                        <th>State</th>
                        <th>Edit</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (Location a in Locations)
                    {
                    <tr>
                        <td>@a.Id</td>
                        <td>@a.City</td>
                        <td>@a.State</td>
                        <td>
                            <button class="btn btn-sm btn-warning"
                                    @onclick="@(() => Edit(a))">
                                Edit
                            </button>
                        </td>
                    </tr>
                    }
                </tbody>
            </table>
        </div>
    </div>
</div>

@code {
    [Inject]
    public DataContext Context { get; set; }

    public Location LocationData = new Location();

    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";

    public void HandleValidSubmit()
    {
        if (Id == 0)
            Context.Add(LocationData);
        Context.SaveChanges();

        UpdateBindings(0, "/ManageLocation", "Form Data Submitted");
        UpdateLocations();
    }

    public void HandleInvalidSubmit() => FormSubmitMessage = "Invalid Data Submitted";

    public List<Location> Locations = new List<Location>();

    protected override void OnInitialized()
    {
        UpdateLocations();
    }

    public void UpdateLocations()
    {
        if (Id != 0)
            LocationData = Context.Location.Where(a => a.Id == Id).FirstOrDefault();
        Locations = Context.Location.ToList();
    }

    [Parameter]
    public int Id { get; set; }

    [Inject]
    public NavigationManager NavManager { get; set; }

    public void UpdateBindings(int idValue, string NavigationValue, string FormSubmitValue)
    {
        Id = idValue;
        NavManager.NavigateTo(NavigationValue);
        FormSubmitMessage = FormSubmitValue;
        LocationData = new Location();
    }

    public void Edit(Location a)
    {
        UpdateBindings(a.Id, "/ManageLocation/" + a.Id, "Form Data Not Submitted");
        UpdateLocations();
    }
}

The Edit button will call the Edit() hander which will update the bindings and fill the variables with Location records.

Notice the code of the HandleValidSubmit handler. It will check if the Id property is 0, in that case it will insert the record, else it will update the record. This is the main reason why the UpdateBindings function also updates the Id property value so that both insertion and updation of the records can be done from the same method.

Do you want to learn how Blazor and JavaScript work together, see JS Interop – Working with JavaScript in Blazor

The below video shows it’s working. Note down the URL change in the browser when the edit button is clicked.

Deletion of Location Records

The final thing to do here is to create the deletion feature. So, add a delete column to the html table and add a new delete button inside it, also handle it’s click event. The necessary code to be added is shown in highlighted way below.

@page "/ManageLocation/{id:int}"
@page "/ManageLocation"

<link href="/validation.css" rel="stylesheet" />

<div class="container">
    <div class="row">
        <div class="col-sm-12">
            <h1 class="bg-info text-white">Manage Location</h1>
        </div>
        <div class="col-sm-5">
            <h2 class="text-success p-2">@FormSubmitMessage</h2>
            <EditForm Model="LocationData" OnValidSubmit="HandleValidSubmit" OnInvalidSubmit="HandleInvalidSubmit">
                <DataAnnotationsValidator />
                <ValidationSummary />

                <div class="form-group">
                    <label>City</label>
                    <ValidationMessage For="@(() => LocationData.City)" />
                    <InputText class="form-control" @bind-Value="LocationData.City" />
                </div>
                <div class="form-group">
                    <label>State</label>
                    <ValidationMessage For="@(() => LocationData.State)" />
                    <InputText class="form-control" @bind-Value="LocationData.State" />
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">Click</button>
                </div>
            </EditForm>
        </div>
        <div class="col-sm-7">
            <table class="table table-sm table-bordered table-striped ">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>City</th>
                        <th>State</th>
                        <th>Edit</th>
                        <th>Delete</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (Location a in Locations)
                    {
                        <tr>
                            <td>@a.Id</td>
                            <td>@a.City</td>
                            <td>@a.State</td>
                            <td>
                                <button class="btn btn-sm btn-warning"
                                        @onclick="@(() => Edit(a))">
                                    Edit
                                </button>
                            </td>
                            <td>
                                <button class="btn btn-sm btn-danger"
                                        @onclick="@(() => Delete(a))">
                                    Delete
                                </button>
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    </div>
</div>

@code {
    [Inject]
    public DataContext Context { get; set; }

    public Location LocationData = new Location();

    public string FormSubmitMessage { get; set; } = "Form Data Not Submitted";

    public void HandleValidSubmit()
    {
        if (Id == 0)
            Context.Add(LocationData);
        Context.SaveChanges();

        UpdateBindings(0, "/ManageLocation", "Form Data Submitted");
        UpdateLocations();
    }

    public void HandleInvalidSubmit() => FormSubmitMessage = "Invalid Data Submitted";

    public List<Location> Locations = new List<Location>();

    protected override void OnInitialized()
    {
        UpdateLocations();
    }

    public void UpdateLocations()
    {
        if (Id != 0)
            LocationData = Context.Location.Where(a => a.Id == Id).FirstOrDefault();
        Locations = Context.Location.ToList();
    }

    [Parameter]
    public int Id { get; set; }

    [Inject]
    public NavigationManager NavManager { get; set; }

    public void Edit(Location a)
    {
        UpdateBindings(a.Id, "/ManageLocation/" + a.Id, "Form Data Not Submitted");
        UpdateLocations();
    }

    public void Delete(Location a)
    {
        if (a.Id == LocationData.Id)
        {
            UpdateBindings(0, "/ManageLocation", "Form Data Not Submitted");
        }

        Context.Remove(a);
        Context.SaveChanges();
        UpdateLocations();
    }

    public void UpdateBindings(int idValue, string NavigationValue, string FormSubmitValue)
    {
        Id = idValue;
        NavManager.NavigateTo(NavigationValue);
        FormSubmitMessage = FormSubmitValue;
        LocationData = new Location();
    }
}

The deletion of a record is done by the 2 code lines:

Context.Remove(a);
Context.SaveChanges();

The delete handler gets the record info in it’s parameter “a” and uses it to update the bindings and locations to. I have shown the working in the below video.

You can download the source codes:

Download

Conclusion

In this tutorial you learned how to work with Forms and perform Validations on them in Blazor. This is an important topic which you should understand fully. In the next tutorial I will create CRUD operations for the Student entity.

SHARE THIS ARTICLE

  • linkedin
  • reddit
yogihosting

ABOUT THE AUTHOR

I hope you enjoyed reading this tutorial. If it helped you then consider buying a cup of coffee for me. This will help me in writing more such good tutorials for the readers. Thank you. Buy Me A Coffee donate

Comments

  1. Dom says:

    Hi, when I try to enter the command
    “dotnet ef migrations add Migration1”
    I get the error
    “dotnet : Could not execute because the specified command or file was not found.
    At line:1 char:1
    + dotnet ef migrations add Migration1
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (Could not execu… was not found.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

    I have installed all the EF packages and do not know what else may be causing this

    1. yogihosting says:

      Hello Don,

      Make sure to update it to latest version by running the command dotnet tool update --global dotnet-ef. Also note that you need to run migrations from the same directory where your Startup.cs class lives. Refer my tutorial on Migrations – Migrations in Entity Framework Core.

      Thank you

Leave a Reply

Your email address will not be published. Required fields are marked *