Blazor CRUD with Entity Framework Core – Detailed Tutorial

by | Updated on Jun 4, 2020 | Blazor

Building a CRUD Application is like the Hello World for Intermediate Developers. It helps you understand the most common operations of any particular stack. In this tutorial, let’s build a Client-side Blazor CRUD Application that uses Entity Framework Core as it’s Data Access Layer. In our previous articles, we discussed Blazor basics and it’s folder structures.

This is part 3 of my Blazor Blog Series, where we learn together various concepts and implementations of Microsoft’s latest tech, Blazor. Here are the contents of my Blazor Blog Series.

  1. Getting Started with Blazor
  2. Exploring Blazor Project Structure
  3. Blazor CRUD with Entity Framework Core – (You are here)

PS, I recommend using Visual Studio 2019 Community as my IDE. Also, To get Blazor on to your machine, please go through Part 1 of the Blazor Blog Series where the pre-requisites are mentioned.

What we will be Building

I thought it would be nice if I showed you guys what we are going to build before jumping into the tutorial so that you get a good idea on the scope of this tutorial and we will be covering. We will have a simple Blazor CRUD implementation on the Developer entity using Blazor WebAssembly and Entity Framework Core. I will also try to demonstrate certain best practices while developing Blazor CRUD Applications.

Blazor CRUD

Note: Let’s build a complete Blazor Application step by step. I will upload the source code of the Application (Blazor.Learner) over at GitHub. We will be reusing and building on top of this application in our future articles as well. Make sure you guys follow me at GitHub to stay posted.

Creating the Blazor Project

Let’s get started with the Blazor CRUD application by creating a new Blazor App. Follow the screenshots below.

crud new project
crud confirm new
crud blazor

Make sure to check the ASP.NET Core Hosted option. So, we are building a client-side aka Blazor WebAssembly project. But there can be instances where we need the features of an ASP.NET Core Application to support the Blazor Application, like using external APIs for data, Application-specific database, etc. For such requirements, we use the ASP.NET Core Hosted hosting model.

crud blazor structure

You can see that Visual Studio automatically generates 3 different projects, i.e, Client, Server, and Shared.
1. Client – Where the Blazor Application lives along with the Razor components.
2. Server – Mostly used as a container that has ASP.NET Core Features (We use it here for EF Core, Api Controllers, and DB).
3. Shared – As the name suggests, all the entity models will be defined here.

So basically, the Server will have Api endpoints that can be accessed by the Client Project. Please note that this is a single application and runs on the same port. Hence the need for CORS access doesn’t arise. All these Projects will be executed directly on your browser and does not need a dedicated server.

Adding the Model

To begin with our Blazor CRUD Application, let’s add a Developer Model on to our Shared Project under the Models folder (create one).

 public class Developer
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public decimal Experience { get; set; }
    }

Entity Framework Core

Now, go to the server project and install the following required packages to enable EF Core.

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer

Defining Connection String

Navigate to appsettings.json under the Server Project and add the Connection String.

"ConnectionStrings": {
    "DefaultConnection": "<Connection String Here>"
  },

Adding Application Context

We will need a database context to work on the data. Create a new class in the Server Project at Data/ApplicationDBContext.cs

public class ApplicationDBContext : DbContext
    {
        public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options):base(options)
        {
        }
        public DbSet<Developer> Developers { get; set; }
    }

We will be adding DbSet of Developers to our context. Using this context class, we will be able to perform operations in our database that we will generate in some time.

Configuring

We will need to add EF Core and define it’s connection string. Navigate to the Startup.cs found in the server project and add the following line to the ConfigureServices method.

services.AddDbContext<ApplicationDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Generating / Migrating the Database

Now that our Entity Framework Core is all set and up on the ASP.NET Core Application, let ‘s do the migrations and update our database. For this, open up the Package Manager Console and type in the following.

add-migration Initial
update-database

Important – Make sure you have chosen the server project as the default.

crud migrations

Developer Controller.

Now that our database and EF Core is set up, we will build an API Controller that will serve the data to our Blazor client. Create an Empty API Controller, Controllers/DeveloperController.cs

 [Route("api/[controller]")]
    [ApiController]
    public class DeveloperController : ControllerBase
    {
        private readonly ApplicationDBContext _context;

        public DeveloperController(ApplicationDBContext context)
        {
            this._context = context;
        }
    }

Here we have injected a new instance of ApplicationDBContent to the constructor of the controller. Let’s continue by adding each of the endpoints for our CRUD Operations.

Get

An Action method to get all the developers from the context instance.

[HttpGet]
        public async Task<IActionResult> Get()
        {
            var devs = await _context.Developers.ToListAsync();
            return Ok(devs);
        }

Get By Id

Fetches the details of one developer that matches the passed id as parameter.

 [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {
            var dev = await _context.Developers.FirstOrDefaultAsync(a=>a.Id ==id);
            return Ok(dev);
        }

Create

Creates a new Developer with the passed developer object data.

 [HttpPost]
        public async Task<IActionResult> Post(Developer developer)
        {
            _context.Add(developer);
            await _context.SaveChangesAsync();
            return Ok(developer.Id); 
        }

Update

Modifies an existing developer record.

[HttpPut]
        public async Task<IActionResult> Put(Developer developer)
        {
            _context.Entry(developer).State = EntityState.Modified;
            await _context.SaveChangesAsync();
            return NoContent();
        }

Delete

Deletes a developer record by Id.

[HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            var dev = new Developer { Id = id };
            _context.Remove(dev);
            await _context.SaveChangesAsync();
            return NoContent();
        }

Getting Started with Blazor CRUD

With all that out of the way, let’s continue building our Blazor CRUD Application. Our Agenda is to do CRUD Operations on the Developer Entity. Essentially we have completed our data layer. Let’s now build the UI.

Adding a new Navigation Menu Entry

We will have to add a new entry in the navaigation menu sidebar to access the Pages. On the client project, Navigate to Shared/NavMenu.razor and add a similar entry to the list.

 <li class="nav-item px-3">
            <NavLink class="nav-link" href="developer">
                <span class="oi oi-people" aria-hidden="true"></span> Developers
            </NavLink>
        </li>
crud nav

Shared Namespaces

We will be using the Shared/Models/Developer.cs Model class throughout this tutorial. Let’s add it’s namespace to the _Imports.razor, so that it can be accessed at all our new components.

@using Blazor.Learner.Shared.Models

Proposed Folder Structure

This is considered as one of the better practices. The idea goes like this.

  1. Have a folder under pages that will be specific for an entity / feature. In our case, we have Developer as the entity.
  2. Under this folder, we try to include all the concerned Razor components.
  3. If you are already an ASP.NET Core Developers, you would know that while implementing standard CRUD, we have many similar things (UI) for Create and Update Forms. For this, we create a common component called as Form that will be shared by the Create and Edit components.
  4. FetchData is to retreive all the data and display it onto a Bootstrap table.
crud folder structure 1

FetchData Component

We will start off by building our Index Component that fetches all the developers from the database. Let’s call it the FetchData Component. Create a new Blazor Component under Pages, Pages/Developer/FetchData.razor

@page "/developer"
@inject HttpClient client
@inject IJSRuntime js

<h3>Developers</h3>
<small>Add as many developers as you wish.</small>
<div class="form-group">
    <a class="btn btn-success" href="developer/create"><i class="oi oi-plus"></i> Create New</a>
</div>
<br>

@if (developers == null)
{
    <text>Loading...</text>
}
else if (developers.Length == 0)
{
    <text>No Records Found.</text>
}
else
{
    <table class="table table-striped">
        <thead>
            <tr>
                <th>Id</th>
                <th>First Name</th>
                <th>Last Name</th>
                <th>Email</th>
                <th>Experience (Years)</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (Developer dev in developers)
            {
                <tr>
                    <td>@dev.Id</td>
                    <td>@dev.FirstName</td>
                    <td>@dev.LastName</td>
                    <td>@dev.Email</td>
                    <td>@dev.Experience</td>
                    <td>
                        <a class="btn btn-success" href="developer/edit/@dev.Id">Edit</a>
                        <button class="btn btn-danger" @onclick="@(() => Delete(dev.Id))">Delete</button>
                    </td>
                </tr>
            }

        </tbody>
    </table>
}
@code {
    Developer[] developers { get; set; }
    protected override async Task OnInitializedAsync()
    {
        developers = await client.GetFromJsonAsync<Developer[]>("api/developer");
    }

    async Task Delete(int developerId)
    {
        var dev = developers.First(x => x.Id == developerId);
        if (await js.InvokeAsync<bool>("confirm", $"Do you want to delete {dev.FirstName}'s ({dev.Id}) Record?"))
        {
            await client.DeleteAsync($"api/developer/{developerId}");
            await OnInitializedAsync();
        }
    }
}

Line 8 – Button to Create a new Developer
Line 34 – Iteration for a new Row for each developer record.
Line 43 – An Edit Button for every record that navigates to …developer/edit/{id}
Line 44 – Delete Button for the Record. This Button invokes a Delete Method written in the Code area of this component.

Line 53 – Defining a list of Developers.
Line 54-55 – This is the function that gets fired on page load. Here we use the HTTP Client object to retrieve data from the API endpoint, api/developer.
Line 59-67 – A standard delete function that gets invoked on a button click.
Line 61- An Object of IJSRuntime that invoked the confirmation dialog.

What is IJSRuntime?

Previously, we had learnt that it is also possible to execute Javascripts in our Blazor Application.The IJSRuntime interface acts like a gateway to JS. This object gives us methods to invoke JS functions in Blazor applications.

Let’s build our application and run it.

crud index

Your first CRUD Component ready in no time! ๐Ÿ˜€ You can see that we don’t have any record. Let’s add a Create Razor Component to be able to add new entries to the database.

But before that, we will have to build a common Form that will be shared by Create and Edit Componets.

Form Component

Add a new Razor component at Pages/Developer/Form.razor

<EditForm Model="@dev" OnValidSubmit="@OnValidSubmit">
    <DataAnnotationsValidator />
    <div class="form-group">
        <label>First Name :</label>
        <div>
            <InputText @bind-Value="@dev.FirstName" />
            <ValidationMessage For="@(() => dev.FirstName)" />
        </div>
    </div>
    <div class="form-group ">
        <div>
            <label>Last Name :</label>
            <div>
                <InputText @bind-Value="@dev.LastName" />
                <ValidationMessage For="@(() => dev.LastName)" />
            </div>
        </div>
    </div>
    <div class="form-group ">
        <div>
            <label>Email :</label>
            <div>
                <InputText @bind-Value="@dev.Email" />
                <ValidationMessage For="@(() => dev.Email)" />
            </div>
        </div>
    </div>
    <div class="form-group ">
        <div>
            <label>Experience :</label>
            <div>
                <InputNumber @bind-Value="@dev.Experience" />
                <ValidationMessage For="@(() => dev.Experience)" />
            </div>
        </div>
    </div>

    <button type="submit" class="btn btn-success">
        @ButtonText
    </button>

</EditForm>


@code {
    [Parameter] public Developer dev { get; set; }
    [Parameter] public string ButtonText { get; set; } = "Save";
    [Parameter] public EventCallback OnValidSubmit { get; set; }
}

Line 1 – EditForm tag that takes in a Developer Model and has a function call to submit.
Line 2 – Validation.
Line 3-9 – A Text Box for the First Name of the developer. Notice that we have bound it to the FirstName property of the model.
Line 28-36 – An Input for numbers.

Line 46 – Developer Object.
Line 47 – Button Text Caption Defaults.
Line 48 – Here is the Method to be called on submitting the form.

Thinking about this, I feel this is quite similar to the concepts of Interface and Concrete classes. Understand it this way. Form Component is the interface which has a blueprint of the properties and methods needed. And the Create/Edit component would be the Concrete class which has as actual implementation of the interface properties/methods. Doesn’t make sense? Read this paragraph again after going through the Create Component Section.

What is the Parameter tag for?

In Blazor, you can add parameters to any components by decorating it with a Parameter tag. What is does is, it becomes availabe for extternal components to pass in these parameters. In our case, we have defined Developer object as the parameters of the Forms components. So, the other components that will use the Form Components , ie, the Create / Edit Components has an option to pass in the Developer object as a parameter to the Form Compoennts. This helps high re-usability of components.

Create Component

Now, let’s start using the previously created Forms component. We will build an interdace for adding a new developer. Create a new Razor Component, Pages/Developer/Create.razor

@page "/developer/create"
@inject HttpClient http
@inject NavigationManager uriHelper

<h3>Create</h3>

<Form ButtonText="Create Developer" dev="@dev"
            OnValidSubmit="@CreateDeveloper" />

@code {
    Developer dev = new Developer();
    async Task CreateDeveloper()
    {
        await http.PostAsJsonAsync("api/developer", dev);
        uriHelper.NavigateTo("developer");
    }
}

Line 1 – Component Route – ../developer/create
Line 7 – Here we are using the Form Component that we created earlier. To this component tag, we are passing parameters like Button Text, a new Blank Developer Object, and a method that is to be called when a user hits the Create Button of this component,
Line 14 – Post the Data to the DB.
Line 15 – Once Inserted, Navigate back to the FetchData component.

Let’s run the application now. Navigate to the Developers Tab and click on the Create Button. Here add in sample data and hit Create Developer.

crud create
crud data

You will be amazed by the speed of the application. Quite smooth yeah? Also, we have built our Create Component quite good by now. Now let’s build our final component. Update.

Edit Component

Create a new Component under Pages/Developer/Edit.razor

@page "/developer/edit/{developerId:int}"
@inject HttpClient http
@inject NavigationManager uriHelper
@inject IJSRuntime js

<h3>Edit</h3>

<Form ButtonText="Update" dev="dev"
      OnValidSubmit="@EditDeveloper" />

@code {
    [Parameter] public int developerId { get; set; }
    Developer dev = new Developer();

    protected async override Task OnParametersSetAsync()
    {
        dev = await http.GetFromJsonAsync<Developer>($"api/developer/{developerId}");
    }

    async Task EditDeveloper()
    {
        await http.PutAsJsonAsync("api/developer", dev);
        await js.InvokeVoidAsync("alert", $"Updated Successfully!");
        uriHelper.NavigateTo("developer");

    }
}

Line 1 – Component Route – ../developer/edit/{id}
Line 7 – Similar to the precious Create Component, we will use the Forms Component
Line 17 – On getting the parameter from the URL, ie, the developer Id, we retrieve that particular record from our API endpoint. This data will be filled on the form by default.
Line 20- 26 – The method that fires up when the user clicks on the Update Button.
Line 22 – Post the Edited data to the API.
Line 23 – Displays an alert message showing “Updated Successfully!”
Line 24 – Redirects to the FetchData component.

Let’s run the application. Naviage to Developer. and follow the below steps.

crud edit button
crud edit
crud alert
crud updated

Summary

By now we are able to Create a Complete Blazor CRUD Application from scratch. It was quite easy too, yeah? We have covered basic concepts of Blazor CRUD and it’s tags and component reusability. We have also seen a good practice folder structure that is quite applicable for Blazor CRUD Applications in general. I hope you guys have understood and enjoyed this detailed tutorial. You can find the completed source code here. As the next step, we will try to implement JWT Authentication in this same Blazor CRUD Project. We will be doing this in the next of the Blazor Blog Series.

How do you like my Blazor Blog Series? Is it easy to grasp? Do you have any other queries / suggestions for me? Feel free to leave them below in the comments section. Happy Coding ๐Ÿ™‚

If you found this article helpful,

Buy me a coffeeBuy me a coffee

33 Comments

  1. Thomas Bazan

    Good tutorial, helped me to understand how to use Blazor a lot.

    Had to Install-Package Microsoft.EntityFrameworkCore.SqlServer in order to make use of “UseSqlServer()”-method.

    Thx for your effort.

    Reply
    • Mukesh Murugan

      Oops, Guess I missed it out. I have updated the Article with the additional required package. Thanks for pointing it out! Blazor is quite an impressive stack, right? ๐Ÿ˜€ Thanks!

      Reply
  2. Spencer

    Nice job, I love this. Do you have an example of the same thing using the Blazor Server? I think by making one and comparing the webassembly and server versions it can clearly depict the applicable differences and would make this a complete Blazor tutorial.

    Once again, great job.

    Reply
    • Mukesh Murugan

      Hey, Thanks! I am on the process of building content on Blazor. Yes, I will be doing the Server variant also quite soon.

      Reply
  3. Nicolas

    Thank you! This is the most well structured tutorial I’ve seen so far.

    Super great!

    Reply
    • Mukesh Murugan

      Thanks a lot. There is Quite more content to come on Blazor ๐Ÿ˜€ ๐Ÿ˜€

      Reply
  4. Ted

    Very nice walkthrough, Mukesh, thank you for doing this.

    Two questions:
    1. Is scaffolding an option to build the Create/Edit/Delete CRUD pages?
    2. Why javascript? Blazor is specifically designed to use C# instead of javascript, so why not C#?

    Thank you again.

    Reply
    • Mukesh Murugan

      1. As of now, I believe it is not possible to scaffold Razor/ Blazor Components. There might be ways like maybe a code snippet template on VS?
      2. Honestly, I believe Blazor is still a not well-matured tech. That is exactly why Blazor supports JS interop. There is still quite a lot of work pending on Blazor. But I guess I have used C# almost everywhere except for the alert calls in this tutorial.

      Thanks for the feedback Ted ๐Ÿ˜€

      Reply
  5. Michaล‚

    Great job! Thank you

    Reply
    • Mukesh Murugan

      Thank you too, Michal! I hope the guide is quite clear.

      Reply
  6. Godwin

    Good job. I took some time to follow through and it was COOL. I look forward to getting more tutorials from you in more advanced projects. Your tutorial is detailed. Thank you

    Reply
    • Mukesh Murugan

      Thanks Godwin! I am preparing the next article “Blazor WebAssembly Authentication”. Will be posted in a day ๐Ÿ˜€

      Reply
  7. stefanov

    Very clear article! Can’t wait to read more!

    Reply
    • Mukesh Murugan

      Hey. Thanks for the feedback! More coming soon ๐Ÿ˜€

      Reply
  8. Robby Robson

    A few comments:
    1. I finally finished the tutorial after having pretty badly mucked initial migration. Turns out I took you too literally in the appsettings.json and left my configuration string as the string “DefaultConnection”! Once I went into SQL Server and created an empty database, I then got the expected “No Records Found” result from Fetch Data.
    2. The tutorial was often silent on the using statements that were necessary to get a clean build. It is clear the tutorial was not aimed at less experienced developers like me. It would be helpful to do a little optional hand holding for folks like me just in case they were stymied by the errors.
    3. Found a strange action by VS 2019 v16.6.2. In creating the “Data” folder in the shared project, I inadvertently left the default name of the new folder (NewFolder) on the folder and continued with the tutorial.
    Later I saw my error and renamed the NewFolder to “Data”, which should have originally been the name. However, VS 2019 V16.6.2 does not respect that new name. It seems that the name “NewFolder” is somehow “sticky” in VS 2019 and I had to revert the name back from “Data” to “NewFolder” for everything to build properly (and get the ApplicationDbContext.cs to be recognized).

    Reply
    • Mukesh Murugan

      Hi, Great to hear.

      I will try to leave a note in my future tutorials to help in adding references and so. I Hope that you liked the implementation.

      Thanks for the feedback.

      Reply
    • Priscilla Cliffton

      Thank you very much Mukesh for the simplicity and clarity of your code.

      Pls let me know your email address.

      Reply
  9. carlospizan

    Hola Mukesh Murugan , me parecio excelente manual, seria bueno un ejemplo maestro detalle

    Gracias

    Reply
  10. Adam

    Hi thank you for this tutorial. Great work am new to blazor and have grasped some concept here.

    Reply
  11. Manfred Smith

    Excellent, straightforward & simple Blazor course. Many thanks

    Reply
  12. Tarik

    Excellent walk through.
    it simple and clear.
    If i may suggest could you please doing like this walk through with A blazor server with the following :
    1- Paging functionality – in its simple way – because this is an essential with huge data.
    2- Using TailwindCss A utility-first CSS framework, it is great and powerful tools.
    Thank you very much for sharing this article.
    Best Regards.

    Reply
    • Mukesh Murugan

      Hi,
      I will be posting more on Blazor soon.
      Thanks for the feedback.
      Regards

      Reply
  13. Jim

    I like what you guys tend to be up too. Such clever work and exposure!
    Keep up the wonderful works guys I’ve included you guys to our
    blogroll. Hello, I check your blog like every week. Your
    story-telling style is awesome, keep doing what youโ€™re doing!
    I could not resist commenting. Exceptionally well written! http://linux.com

    Reply
  14. Ahmed Nazmy

    Application shows message ‘An unhandled error has occurred’, when inspecting through google chrome I see the details below:

    crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
    Unhandled exception rendering component: Response status code does not indicate success: 500 (Internal Server Error).
    System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).
    at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode () in :0
    at System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsyncCore[T] (System.Threading.Tasks.Task`1[TResult] taskResponse, System.Text.Json.JsonSerializerOptions options, System.Threading.CancellationToken cancellationToken) in :0
    at Blazor.Learner.Client.Pages.Developer.FetchData.OnInitializedAsync () in :0
    at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () in :0
    at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask (System.Threading.Tasks.Task taskToHandle) in :0

    Reply
    • Ruan

      Have the SAME issue where you able to resolve??

      Reply
  15. Paul Hagerty

    good stuff, great work

    Reply
  16. Muhammad Saad

    Very nice article! Line wise description is good
    My Question: On successful insertion, I dont see any record in SQL Server Database…..
    Please guide

    Reply
  17. Ryan Gray

    Excellent tutorial! Very easy to follow along and well structured. I look forward to reading more of your stuff! A few I would love to see which all pertain to the list pages would be:

    1. Implementing search criteria (aka filtering) the data.
    2. Paging
    3. Sorting

    Thanks!

    Reply
  18. josep temprano

    Very Nice introduction to Blazor!
    It’s possible a future article about integrate Blazor in the Clean Architecture.
    Thanks.
    Josep

    Reply

Trackbacks/Pingbacks

  1. .NET Annotated Monthly | June 2020 - .NET Tools Blog.NET Tools Blog - […] Blazor CRUD with EF Core – A detailed tutorial on how to use Blazor with EF when interacting with…

Submit a Comment

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

Follow codewithmukesh

Support Me!

Buy me a coffeeBuy me a coffee

Pin It on Pinterest