Crud Operation in MAUI Blazor Hybrid App using SqlLite

Praveen Kumar KS
13 min readMar 6, 2023

--

  1. Add SqlLite nuget package to the project.
  2. Add splash screen for Android and iOS apps in .csproj file

3. Add image in resource folder.

4. Right click the file and select build action to “MauiSplashScreen”

5. Clear bin and obj folders, to proceed.

6. If images need to be added, add image in the images folder which is located in the resource folder and change the build action to the “MauiImage”, in the properties section

Note For every action changes in the .csproj clear the bin and obj folders.

7. Mention the default layout in the Main.razor file.

<Router AppAssembly="@typeof(Main).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

This appears to be code written in Razor syntax using the Blazor framework. The code sets up a router component that maps incoming URLs to the appropriate component in the application.

The Router component takes an AppAssembly parameter, which specifies the assembly that contains the application's components.

The Found block contains a RouteView component, which renders the component that corresponds to the current route. It takes in the current routeData object as a parameter and a DefaultLayout parameter that specifies the default layout to use for the rendered component.

The FocusOnNavigate component specifies that when a route is navigated to, the focus should be set on the h1 element within the rendered component.

The NotFound block specifies what should be rendered when the requested URL does not match any of the defined routes. It contains a LayoutView component that specifies the layout to use for the error message, and a simple error message in a p element.

Overall, this code sets up a router component that maps incoming URLs to appropriate components and provides a fallback error message for when the requested URL is not found.

MainLayout.razor

@inherits LayoutComponentBase

<div class="page">
<div class="sidebar">
<NavMenu />
</div>

<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>

<article class="content px-4">
@Body
</article>
</main>
</div>

This appears to be a layout component written in Razor syntax using the ASP.NET Core framework.

The @inherits directive specifies that this component inherits from LayoutComponentBase, which is a built-in base class for layout components in ASP.NET Core.

The layout component defines a basic page layout with a sidebar and a main content area. The NavMenu component is rendered inside the sidebar. The main element contains a div with a top-row class that contains a link to the ASP.NET documentation website. The @Body directive is used to render the content of the page inside an article element with a content class.

Overall, this code provides a simple page layout that can be used by other components in the application. By inheriting from LayoutComponentBase, the layout component gains access to useful features such as the ability to specify the title of the page and the ability to add CSS and JavaScript files to the page.

NavMenu:

<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">SQLiteDemoWithBlazorApp</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
@*Crud Operation *@
<div class="nav-item px-3">
<NavLink class="nav-link" href="add_student">
<span class="oi oi-plus" aria-hidden="true"></span> Add Edit Student
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</div>
</nav>
</div>

@code {
private bool collapseNavMenu = true;

private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

This is a Blazor component that defines a navigation menu using Bootstrap styling. The navigation menu includes links to different pages within the Blazor app.

The component is divided into two main parts: the top row and the navigation menu itself.

The top row contains the name of the app and a button to toggle the visibility of the navigation menu. The ToggleNavMenu() method is called when the button is clicked, and it updates the collapseNavMenu field to toggle the visibility of the menu.

The navigation menu is defined using a <nav> element with a flex-column class. Each item in the menu is defined using a <div> element with a nav-item class, and a <NavLink> element is used to create a link to a specific page within the Blazor app. The NavLink component is part of the Blazor routing system and provides a way to navigate between pages without causing a full page refresh.

The NavMenuCssClass property is used to conditionally set the collapse class on the navigation menu based on the value of the collapseNavMenu field. This causes the menu to be hidden or shown based on the state of the component.

Overall, this component provides a useful navigation menu that can be easily customized and extended to fit the needs of a Blazor app.

AddUpdateStudent.razor

@page "/add_student"
@page "/update_student/{StudentID:int}"


@using SQLiteDemoWithBlazorApp.Services
@inject IStudentService StudentService
<h3>AddUpdateStudent</h3>

<div class="form-group">
<label>First Name</label>
<input @bind="firstName" class="form-control" placeholder="First Name">
</div>
<div class="mt-2 form-group">
<label>Last Name</label>
<input @bind="lastName" class="form-control" placeholder="Last Name">
</div>


<div class="mt-2 form-group">
<label>Email address</label>
<input @bind="email" type="email" class="form-control" placeholder="Email">
</div>

<div class="mt-2 form-group">
<label>Gender</label>
<div class=" d-flex flex-row">
<div class="col-6 d-flex justify-content-between">
<div class="form-check">
<input checked="@(gender=="Male")" @onchange="@(()=> setGender("Male"))" class="form-check-input" type="radio" name="flexRadioDefault">
<label class="form-check-label" for="flexRadioDefault1">
Male
</label>
</div>
<div class="form-check">
<input checked="@(gender=="Female")" @onchange="@(()=> setGender("Female"))" class="form-check-input" type="radio" name="flexRadioDefault">
<label class="form-check-label" for="flexRadioDefault2">
Female
</label>
</div>
</div>
</div>
</div>

<button type="submit" @onclick="AddStudentRecord" class="mt-2 btn btn-primary">Submit</button>

This is a Blazor component that allows the user to add or update a student record. The component includes HTML markup for a form that collects information about the student, such as their first and last name, email address, and gender.

The @page directive at the beginning of the file sets up two different URLs that can be used to access the component. The first URL (/add_student) is used when adding a new student record, while the second URL (/update_student/{StudentID:int}) is used when updating an existing record.

The component injects an instance of the IStudentService interface using the @inject directive. This interface provides methods for adding and updating student records.

The component includes several form controls for collecting information about the student. These controls use the @bind directive to bind their values to properties on the component. For example, the first name input control binds to the firstName property.

The component also includes radio buttons for selecting the student’s gender. These radio buttons use the @onchange directive to call a method that sets the gender property on the component.

Finally, the component includes a submit button that calls the AddStudentRecord method when clicked. This method uses the IStudentService interface to add or update the student record in the database.

Note API integration can be added directly through the service call via repository pattern in the particular components or through the class library. If API URL’s handled in class library, then path should be included in _import.razor file.

add c# code in @code {}

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

private string firstName;
private string lastName;
private string email;
private string gender;

private void setGender(string gender)
{
this.gender = gender;
}

protected async override Task OnInitializedAsync()
{
if (StudentID > 0)
{
var response = await StudentService.GetStudentByID(StudentID);
if (response != null)
{
firstName = response.FirstName;
lastName = response.LastName;
email = response.Email;
gender = response.Gender;
}
}
}

The OnInitializedAsync method is an override of the base OnInitializedAsync method provided by the ComponentBase class. It is called when the component is being initialized and can be used to perform any initialization tasks, such as retrieving data from a data source.

In the code snippet you provided, the OnInitializedAsync method is being used to check if a StudentID value has been passed to the component via the URL. If a StudentID value is present, it calls the GetStudentByID method of the IStudentService to retrieve the student record associated with that ID. If a record is found, it populates the component's state with the retrieved data (i.e. firstName, lastName, email, gender).

Note that the await keyword is used when calling the GetStudentByID method, indicating that this is an asynchronous operation. This allows the method to continue executing other code while it waits for the response from the IStudentService.

 private bool Validation()
{
if (string.IsNullOrWhiteSpace(firstName))
{
App.Current.MainPage.DisplayAlert("Validation",
"Enter first name", "OK");
return false;
}
else if (string.IsNullOrWhiteSpace(lastName))
{
App.Current.MainPage.DisplayAlert("Validation",
"Enter last name", "OK");
return false;
}
else if (string.IsNullOrWhiteSpace(email))
{
App.Current.MainPage.DisplayAlert("Validation",
"Enter email", "OK");
return false;
}
else if (string.IsNullOrWhiteSpace(gender))
{
App.Current.MainPage.DisplayAlert("Validation",
"select gender", "OK");
return false;
}
return true;
}

The Validation method is used to validate the input fields in the form. It checks if the first name, last name, email, and gender fields are not empty. If any of these fields are empty, it displays an alert message to the user using the App.Current.MainPage.DisplayAlert method and returns false. If all the fields are filled in, it returns true. This method is used to ensure that the user enters all the required information before submitting the form.

    private async void AddStudentRecord()
{
if (Validation())
{
var studentModel = new Models.StudentModel
{
FirstName = firstName,
LastName = lastName,
Email = email,
Gender = gender,
StudentID = StudentID
};

int response = -1;
if (StudentID > 0)
{
response = await StudentService.UpdateStudent(studentModel);
//update record
}
else
{
response = await StudentService.AddStudent(studentModel);
//add record
}


if (response > 0)
{
firstName = lastName = gender = email = string.Empty;
this.StateHasChanged();
await App.Current.MainPage.DisplayAlert("Record Saved",
"Record Saved To Student Table", "OK");
}
else
{
await App.Current.MainPage.DisplayAlert("Oops",
"Something went wrong while adding record", "OK");
}
}
}

The AddStudentRecord() method is responsible for adding or updating a student record based on the StudentID property.

First, it calls the Validation() method to ensure that all the required fields are entered. Then, it creates a new StudentModel object with the values of the input fields and StudentID.

If StudentID is greater than zero, it means the user is updating an existing student record, so it calls the UpdateStudent() method of the StudentService and passes the studentModel object to it. Otherwise, it calls the AddStudent() method of the StudentService and passes the studentModel object to it.

If the response from the service method is greater than zero, it means the record was successfully added or updated, so it clears the input fields and displays an alert message indicating that the record was saved. Otherwise, it displays an error message.

FetchData.razor

@page "/fetchdata"

@using SQLiteDemoWithBlazorApp.Data
@using SQLiteDemoWithBlazorApp.Models
@using SQLiteDemoWithBlazorApp.Services
@inject IStudentService StudentService
@inject NavigationManager NavManager
<h1>Student List</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (students == null)
{
<p><em>Loading...</em></p>
}
else
{
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Gender</th>
<th>Email</th>
</tr>
</thead>
<tbody>
@foreach (var student in students)
{
<tr>
<td>@student.FirstName</td>
<td>@student.LastName</td>
<td>@student.Gender</td>
<td>@student.Email</td>
<td>
<button type="submit" @onclick="@(()=> EditStudent(student.StudentID))" class="btn btn-primary">Edit</button>
</td>
<td>
<button type="submit" @onclick="@(()=> DeleteStudent(student))" class="btn btn-primary">Delete</button>
</td>
</tr>
}
</tbody>
</table>
</div>
}

This is a Blazor component that displays a list of students. The component fetches the data from a service (IStudentService), injects the service using the @inject directive, and uses the students variable to store the list of students. If the students variable is null, it shows a "Loading..." message. Otherwise, it shows a table that displays the first name, last name, gender, and email of each student. It also provides buttons to edit or delete each student.

The EditStudent method is called when the Edit button is clicked, and it navigates to the "update_student/{StudentID}" page, passing the student's ID as a parameter.

The DeleteStudent method is called when the Delete button is clicked, and it deletes the student from the database using the StudentService and updates the students list.

private List<StudentModel> students;

protected override async Task OnInitializedAsync()
{
students = await StudentService.GetAllStudent();
}

private void EditStudent(int studentID)
{
NavManager.NavigateTo($"update_student/{studentID}");
}

private async void DeleteStudent(StudentModel student)
{
var response = await StudentService.DeleteStudent(student);
if (response > 0)
{
await OnInitializedAsync();
this.StateHasChanged();
}
}

This is a Blazor component code that displays a list of students and allows editing and deleting student records.

The students variable is a list of StudentModel objects that is populated with data from the StudentService in the OnInitializedAsync method.

The EditStudent method is used to navigate to the update student page for a specific student based on the studentID parameter.

The DeleteStudent method is used to delete a specific student record from the database based on the student parameter. The method calls the DeleteStudent method of the StudentService and updates the list of students if the operation is successful. Finally, the StateHasChanged method is called to notify Blazor that the state of the component has changed and it needs to re-render the UI.

StudentService.cs

public class StudentService : IStudentService
{
private SQLiteAsyncConnection _dbConnection;

public StudentService()
{
SetUpDb();
}

private async void SetUpDb()
{
try
{
if (_dbConnection == null)
{
string dbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Student.db3");
_dbConnection = new SQLiteAsyncConnection(dbPath);
await _dbConnection.CreateTableAsync<StudentModel>();
}
}
catch(Exception e)
{

}

}

public async Task<int> AddStudent(StudentModel studentModel)
{
return await _dbConnection.InsertAsync(studentModel);
}

public async Task<int> DeleteStudent(StudentModel studentModel)
{
return await _dbConnection.DeleteAsync(studentModel);
}
public async Task<int> UpdateStudent(StudentModel studentModel)
{
return await _dbConnection.UpdateAsync(studentModel);
}
public async Task<List<StudentModel>> GetAllStudent()
{
return await _dbConnection.Table<StudentModel>().ToListAsync();
}

public async Task<StudentModel> GetStudentByID(int StudentID)
{
var student = await _dbConnection.QueryAsync<StudentModel>($"Select * From {nameof(StudentModel)} where StudentID={StudentID} ");
return student.FirstOrDefault();
}
}

The StudentService class is responsible for handling database operations related to StudentModel. It implements the IStudentService interface, which defines the necessary methods for CRUD (Create, Read, Update, Delete) operations.

The SetUpDb() method initializes the SQLite database connection and creates the StudentModel table if it doesn't exist.

The AddStudent(StudentModel studentModel) method inserts a new record into the StudentModel table and returns the number of rows affected.

The DeleteStudent(StudentModel studentModel) method deletes a record from the StudentModel table based on the StudentModel parameter passed and returns the number of rows affected.

The UpdateStudent(StudentModel studentModel) method updates an existing record in the StudentModel table based on the StudentModel parameter passed and returns the number of rows affected.

The GetAllStudent() method retrieves all records from the StudentModel table and returns a list of StudentModel.

The GetStudentByID(int StudentID) method retrieves a record from the StudentModel table based on the StudentID parameter passed and returns a single StudentModel object.

IStudentService.cs

public interface IStudentService
{
Task<List<StudentModel>> GetAllStudent();
Task<StudentModel> GetStudentByID(int StudentID);
Task<int> AddStudent(StudentModel studentModel);
Task<int> UpdateStudent(StudentModel studentModel);
Task<int> DeleteStudent(StudentModel studentModel);
}

This is an interface for the StudentService class, which defines the methods that should be implemented by any class that provides student-related services. The interface includes methods to get all students, get a student by ID, add a new student, update an existing student, and delete a student.

public class StudentModel
{
[PrimaryKey,AutoIncrement]
public int StudentID { get; set; }

public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Gender { get; set; }
}

This is a C# class called “StudentModel” that represents a student in a database. The class has four properties: “StudentID”, “FirstName”, “LastName”, “Email”, and “Gender”.

The “StudentID” property is an integer and is marked as the primary key of the table. The “AutoIncrement” attribute indicates that this property will be automatically incremented by the database system for each new student added to the table.

The “FirstName”, “LastName”, “Email”, and “Gender” properties are all strings and represent the first name, last name, email address, and gender of the student, respectively. These properties do not have any special attributes or constraints attached to them.

This class is likely used in conjunction with an Object-Relational Mapping (ORM) framework such as Entity Framework or SQLite-net to map the properties of the class to columns in a database table.

To run the application choose the Debugging option:

Output looks like Output 1.0. As per the document crud operation included in Add Edit Student Tab and it mentioned in NavMenu Component.

Enter the required fields, shown in Output 1.1.

Output 1.2 shows the alert message for the required field.

Output 1.4 shows the alert message after submit action.

In Fetch data we can see the added data.

The same result will appear in Android Application.

Output 1.5

Output 1.5, shows the result of Android application.

Output 1.6

Output 1.6, shows the result of Mac Catalyst.

In conclusion, CRUD operations are a fundamental aspect of most applications, and Maui Blazor provides developers with an easy way to implement these operations in a cross-platform environment. With MAUI Blazor, developers can create UIs that allow users to create, read, update, and delete data from a database using C# code.

The process of implementing CRUD operations in MAUI Blazor involves creating a data model, creating a database context, and then creating controller methods that can be used to interact with the database. Developers can then use these methods to create UI components that allow users to perform CRUD operations on the data.

Overall, MAUI Blazor makes it easy for developers to create cross-platform applications that can perform CRUD operations on data. This can help to improve productivity, reduce development costs, and provide a better user experience for users.

Need more stuff regarding Login flow and storage, visit this link Login flow in MAUI Blazor.

--

--