.NET Core CRUD

.NET Core Single-Page CRUD Application. It's a user-friendly, efficient, and seamless solution that empowers you to Create, Read, Update, or Delete user data, all from a single page. Say goodbye to the complexities of routing – we've streamlined everything for you!


Introduction

Our ASP.NET Core Single-Page CRUD Application is a testament to the capabilities of the ASP.NET Core framework. You're getting a robust foundation for building CRUD applications with great user interface.

Whether you're a newcomer to ASP.NET Core or an experienced developer, this example application lets you focus on what matters most. Explore the code, customize it to your specific requirements, or build upon its streamlined structure.


CRUD Requirements

NOTE: In AspNetCoreFull.csproj file all required tools and dependencies are added for Microsoft , Entity Core framework & their tools and SQLite database must be included for enabling dotnet to use them

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UserSecretsId>2ac58585-d1b3-40ff-a284-c11f26889b31</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SQLite" Version="8.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
    <PackageReference Include="System.Data.SQLite" Version="1.0.118" />
      <None Update="User.Data.db">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

NOTE: Run the given CLI commands to install .NET packages and it would inject the packages in .csproj file


dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.SQLite
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package System.Data.SQLite

NOTE: SQLite connection with user context and SeedData Initialization Code is added in Program.cs which enables a connection between Model , Context and database.


using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using AspnetCoreFull.Data;
using AspnetCoreFull.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

// Add services to the container.
builder.Services.AddRazorPages();

// Sqlite database connection with UserContext class
builder.Services.AddDbContext<UserContext>(options =>
{
  options.UseSqlite(builder.Configuration.GetConnectionString("UserContext") ?? throw new InvalidOperationException("Connection string 'UserContext' not found."));
}, ServiceLifetime.Scoped);

var app = builder.Build();
// Create a scope for the service provider and call the SeedData.Initialize method
using (var scope = app.Services.CreateScope())
{
  var services = scope.ServiceProvider;

  SeedData.Initialize(services);
}

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
  app.UseExceptionHandler("/Error");
  // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
  app.UseHsts();
}

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

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
        

Key Features

  • Create: Add new user records effortlessly, without navigating between multiple screens
  • Read: Access and view user data with ease, all in one organized and intuitive interface
  • Update: Modify user details quickly and conveniently
  • Delete: Safely remove user records from the database, without ever leaving your current view

To access User CRUD app, click the Users (CRUD) link in the left sidebar or add /CRUD/UserCRUD to the URL.


Show Users

The first thing you will see is a list of existing users. We have used Datatable in our user management table.

The data is seeded in database by Program.cs and that data is shown by UserCRUD.cs Model's OnGetAsync() which gives user data and all other counts in widgets as well.

UserCRUD.cs file uses Helpers from UserCRUDHelpers.cs file for GenerateUserAvatar and FormatUSPhoneNumber helper functions.

Here you can see how we implemented datatable with .NET Core CRUD App.

@* Users DataTable *@
@if (Model.Users != null && Model.Users.Count > 0)
{
  <div class="card">
  <div class="card-header">
    <h5 class="card-title mb-0">Search Filter</h5>
  </div>
  <div class="card-datatable table-responsive pb-0">
    <table id="userTable" class="table">
      <thead class="table-light">
        <tr>
          <th></th>
          <th>Id</th>
          <th>User</th>
          <th>Email</th>
          <th>Verified</th>
          <th>Contact</th>
          <th>Role</th>
          <th>Plan</th>
          <th>Action</th>
        </tr>
      </thead>
      <tbody>
        @foreach (var user in Model.Users)
          {
            <tr>
              <td></td>
              <td>@user.Id</td>
              <td class="user-name text-nowrap">@Html.Raw(UserCRUDHelpers.GenerateUserAvatar(user))</td>
              <td class="text-truncate">@user.Email</td>
              <td>@if (user.IsVerified)
                {
                  <i class='ri-shield-check-line ri-24px text-success'><span class="d-none">1</span></i>
                }
                else
                {
                  <i class="ri-shield-line ri-24px text-danger"><span class="d-none">0</span></i>
                }
                <input type="hidden" class="user-verified-@user.Id" data-is-verified="@user.IsVerified" />
              </td>
              <td class="text-nowrap">@UserCRUDHelpers.FormatUSPhoneNumber(user.ContactNumber)</td>
              <td class="text-capitalize">@user.SelectedRole</td>
              <td class="text-capitalize">@user.SelectedPlan</td>
              <td class="text-nowrap">
                @* Edit Button *@
                <button class="btn btn-sm btn-icon edit-user-button" data-bs-toggle="offcanvas"
                  data-bs-target="#editUserOffcanvas" id="@user.Id-editUser"><i
                    class="ri-edit-box-line ri-20px"></i></button>
                @* Delete Button *@
                <form method="post" asp-page-handler="Delete" asp-route-id="@user.Id" id="@user.Id-deleteForm"
                  onsubmit="showSuccessAlert('Deleted');" class="d-inline">
                  <button class="btn btn-sm btn-icon" id="@user.Id-deleteUser" onclick="showDeleteConfirmation('@user.Id')">
                    <i class="ri-delete-bin-7-line ri-20px"></i>
                  </button>
                </form>
                @* Dropdown Button *@
                <button class="btn btn-sm btn-icon dropdown-toggle hide-arrow" data-bs-toggle="dropdown"><i
                    class="ri-more-2-line ri-20px"></i></button>
                <div class="dropdown-menu dropdown-menu-end m-0">
                  <a href="/Apps/Users/View/Account" class="dropdown-item">View</a>
                  <a href="javascript:void(0);" class="dropdown-item">Suspend</a>
                </div>

              </td>
            </tr>
          }
        </tbody>
      </table>
    </div>
  </div>
}
else
{
  <div class="card">
  <div class="card-header">
    <h5 class="card-title mb-0">Search Filter</h5>
  </div>
  <div class="card-datatable table-responsive pb-0">
    <table id="userTable" class="table">
      <thead class="border-top">
        <tr>
          <th></th>
          <th>Id</th>
          <th>User</th>
          <th>Email</th>
          <th>Verified</th>
          <th>Contact</th>
          <th>Role</th>
          <th>Plan</th>
          <th>Action</th>
        </tr>
      </thead>
    </table>
  </div>
</div>
}

We have used datatables only to handle UI , export options , pagination , search, and page length whereas all data feeding , updating or deleting is done by UserCRUD.cs Model

Note: We have initialized datatables with column options to achieve better UI for User management app

The function OnGetAsync() lists the data in datatable from Sqlite database and therefore the data is shown


public async Task OnGetAsync()
{
  // Load all users from the database
  Users = await _context.User.ToListAsync();

  // Create select lists for available roles and plans
  AvailableRolesSelectList = new SelectList(GetAvailableRoles());
  AvailablePlansSelectList = new SelectList(GetAvailablePlans());

  // Calculate the count of duplicate user names
  DuplicateUserCount = Users.GroupBy(u => u.UserName).Sum(g => g.Count() - 1);

  // Get the total user count
  TotalUserCount = Users.Count;

  // Get the count of verified users
  VerifiedUserCount = Users.Count(u => u.IsVerified);

  // Get the count of unverified users
  UnverifiedUserCount = Users.Count(u => !u.IsVerified);
}

Create/Update Users

You can add new ones by clicking the Add New User button (above the table on the right). On the Add user right sidebar, you will find a form that allows you to fill out the user's name, email, contact, role, plan and verification status.

We have provided an update/edit icon in table to update the user's information.


<!-- Create User Form Offcanvas -->
<div class="offcanvas offcanvas-end" tabindex="-1" id="createUserOffcanvas" aria-labelledby="createUserOffcanvasLabel">
  <div class="offcanvas-header">
    <h5 class="offcanvas-title" id="createUserOffcanvasLabel">Create User</h5>
    <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
  </div>
  <div class="offcanvas-body mx-0 flex-grow-0">
    <form method="post" id="createUserForm">
      ... ...
    </form>
  </div>
</div>

<!-- Edit User Form Offcanvas -->
<div class="offcanvas offcanvas-end" tabindex="-1" id="editUserOffcanvas" aria-labelledby="editUserOffcanvasLabel">
  <div class="offcanvas-header">
    <h5 class="offcanvas-title" id="editUserOffcanvasLabel">Edit User</h5>
    <button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
  </div>
  <div class="offcanvas-body mx-0 flex-grow-0">
    <form method="post" asp-page-handler="EditOrUpdate" asp-route-id="" id="editUserForm">
      ... ...
    </form>
  </div>
</div>
        

On clicking , It would open Create User Offcanvas form which on submitting would call OnPostAsync() that would create new user in context which is then updated to database.

On clicking , It would open Create User Offcanvas form which would get user from context with help of its id and then display current data in edit offcanvas form using JS function .On submit, Edit form would call OnPostEditOrUpdateAsync(int id) which would make updates to user data if any and save it to context and database with help of TryUpdateModelAsync() method.


// The OnPostAsync method is called when the Create User form is submitted
public async Task OnPostAsync()
{
  if (NewUser?.UserName != null && NewUser?.Email != null)
  {
    // Add a new user to the database
    _context.User.Add(NewUser);
    await _context.SaveChangesAsync();
    return RedirectToPage();
  }

  return Page();
}

// The OnPostEditOrUpdateAsync method is called when the Edit User form is submitted
public async Task OnPostEditOrUpdateAsync(int id)
{
  var userToUpdate = await _context.User.FindAsync(id);
  if (userToUpdate == null)
  {
    return NotFound();
  }

  // Update user properties based on form data
  string isVerifiedString = Request.Form["user.IsVerified"];
  userToUpdate.IsVerified = (isVerifiedString == "on") ? true : false;

  // Update the user in the database and save changes
  await TryUpdateModelAsync(userToUpdate, "user", u => u.UserName, u => u.Email, u => u.IsVerified, u => u.ContactNumber, u => u.SelectedRole, u => u.SelectedPlan);
  await _context.SaveChangesAsync();
  return RedirectToPage();
}
// Function to handle the "Edit User" Offcanvas Modal
const handleEditUserModal = editButton => {
  // Get the user details from the table
  ... ...
};

// Attach event listeners for "Edit User" buttons (pencil icon)
const editUserButtons = document.querySelectorAll("[id$='-editUser']");
editUserButtons.forEach(editButton => {
  editButton.addEventListener('click', () => handleEditUserModal(editButton));
});

We are validating create/update form using the formValidation javascript plugin. After successfully validating the form, we perform on -> core.form.valid or core.form.invalid depending on which , create or edit form is submitted and their respective functions are called.

JS for using form validation in Users CRUD .NET Core is given below which is similar for create/edit.


// Get the Create for validation
const createNewUserForm = document.getElementById('createUserForm');

// Initialize FormValidation for create user form
const fv = FormValidation.formValidation(createNewUserForm, {
  fields: {
    'NewUser.UserName': {
      validators: {
        notEmpty: {
          message: 'Please enter a user name'
        },
        stringLength: {
          min: 6,
          max: 30,
          message: 'The user name must be more than 6 and less than 30 characters long'
        }
      }
    },
    ...
  },
  plugins: {
    ...
  }
})
  .on('core.form.valid', function () {
    // if fields are valid then
    submitFormAndSetSuccessFlag(createNewUserForm, 'newUserFlag');
  })
  .on('core.form.invalid', function () {
    // if fields are invalid
    isFormValid = false;
  });

Delete Users

To delete the user, we have provided a delete icon.

We have used sweetalert2 to get the delete confirmation.

<!-- Delete Button -->
<form method="post" asp-page-handler="Delete" asp-route-id="@user.Id" id="@user.Id-deleteForm"
onsubmit="showSuccessAlert('Deleted');" class="d-inline">
<button class="btn btn-sm btn-icon" id="@user.Id-deleteUser" onclick="showDeleteConfirmation('@user.Id')">
  <i class="ri-delete-bin-7-line ri-20px"></i>
</button>
</form>

// Functions to handle the Delete User Sweet Alerts (Delete Confirmation)
function showDeleteConfirmation(userId) {
  event.preventDefault(); // prevent form submit
  const userName = document.querySelector(`.user-name-full-${userId}`).innerText;
  Swal.fire({
    ...
}

// Sweet Alert Success Function (User Deleted/Created/Updated)
function showSuccessAlert(message) {
  var name = message[0].toUpperCase() + message.slice(1);
  Swal.fire({
    ...
    }
  });
}
        

For delete when is clicked then showDeleteConfirmation(userId) is called and if it is confirmed then OnPostDeleteAsync(int id) is called which gets user from Id , deletes user and save changes to context/database


// The OnPostDeleteAsync method is called when the Delete User form is submitted
public async Task OnPostDeleteAsync(int id)
  {
    var user = await _context.User.FindAsync(id);

    if (user != null)
    {
      // Remove the user from the database
      _context.User.Remove(user);
      await _context.SaveChangesAsync();
    }

    return RedirectToPage();
  }

SQLite

Migration and Database setup

dotnet ef migrations add InitialCreate
dotnet ef database update
        

The migrations command generates code to create the initial database schema. The schema is based on the model specified in DbContext. The InitialCreate argument is used to name the migrations. Any name can be used, but by convention a name is selected that describes the migration.

The update command runs the Up method in migrations that have not been applied. In this case, update runs the Up method in the Migrations/_InitialCreate.cs file, which creates the database.

Requirements

Download Db browser for SQLite to view & manage database at SQlite Browser.

Here, ConnectionStrings=>Data Source is set to Database name which is used in Program.cs to establish connection between database and table allowing CRUD operations.


{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "UserContext": "Data Source=User.Data.db" // this should match your database name!
  }
}
        

// Sqlite database connection with UserContext class
builder.Services.AddDbContext(options =>
{
  options.UseSqlite(builder.Configuration.GetConnectionString("UserContext") ?? throw new InvalidOperationException("Connection string 'UserContext' not found."));
}, ServiceLifetime.Scoped);

var app = builder.Build();
// Create a scope for the service provider and call the SeedData.Initialize method
using (var scope = app.Services.CreateScope())
{
  var services = scope.ServiceProvider;

  SeedData.Initialize(services);
}
        

FAQs

How to implement user CRUD in starter kit?

To set up CRUD in starter kit (AspNetCoreStarter), consider the following steps:

  • Copy the given directories from /AspNetCoreFull and paste in /AspNetCoreStarter :
    1. /Data
    2. /Models
    3. /Pages/CRUD
  • locate Program.cs in /AspnetCoreStarter and then replace it with Program.cs file from AspNetCoreFull.
  • For seeding , initiation(creation) and migration of SQLite database run below given commands:
    dotnet ef migrations add InitialCreate
    dotnet ef database update
    
  • Running Migration command will create /Migrations in which its corresponding context files will be created whereas User.Data.db file will be created in /AspnetCoreStarter.
  • User.Data.db-shm & User.Data.db-wal are generated along with User.Data.db as its SQLite database enhancement files which help in caching or storing data and enable smooth experience.
  • Download Db browser for SQLite at SQlite Browser to view & manage database ! Locate your database User.Data.db from /AspnetCoreStarter and you can operate the database from browser itself!

Now run your starter kit with dotnet run or dotnet watch and you will have CRUD in your starter kit!

© 2017- Pixinvent, Hand-crafted & Made with ❤️