Search code examples
c#sqlrazorblazor

How to send data from local storage to a SQL database from a button click in a Blazor app


I'm currently developing a Blazor application for electronic recycling. The app allows employees to input information into a form, and all the entered data is stored in local storage. After inputting the data, users are redirected to a summary page where they can confirm the accuracy of the entered information.

The current functionality is working well, but I'm facing a challenge with the submit button. I want the app to perform the following actions upon submission:

Check the database to see if the CPSNumber already exists.

  • If the CPSNumber is found, update the corresponding columns in the database with the information from the form.

  • If the CPSNumber is not found, add the columns from the summary page to the database.

Could you provide guidance or code snippets on how to implement this logic in my Blazor app?

EDIT Here is the code for the form the employees fill out:

@page "/form"
@attribute [Authorize]
@using System.Text.Json;
@using Blazored.LocalStorage;
@inject IJSRuntime JSRuntime
@inject NavigationManager NavigationManager
@inject DialogService DialogService
@inject ContextMenuService ContextMenuService
@inject TooltipService TooltipService
@inject NotificationService NotificationService
@inject NavigationManager NavManager
@inject Blazored.LocalStorage.ILocalStorageService oLocalStore


<PageTitle>Form</PageTitle>
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h1 class="h3" style="display: block; text-align: center">Welcome Chris DeBrodie!</h1>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <div class="form-group">

            </div>
        </div>
    </div>
    <div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="form-group">
             @foreach (var (inputData, index) in inputCardData.Select((data, index) => (data, index)))
{
    <div class="col-md-12">
        @if (index == 0)
        {
            <!-- Render the dropdown only for the first card -->
            <RadzenDropDown TValue="string" ValueProperty="BuildingName" TextProperty="BuildingName" Data="@buildingLists" @bind-Value="inputData.BuildingName"  AllowSelectAll="false" Placeholder="School Name" Style="width: 100%; text-align: center" />
        }
        
        <RadzenCard Variant="Radzen.Variant.Filled">
            <div class="form-group">
                <RadzenAutoComplete TextProperty="CPSNumber" Data="@surplusItemsNews" @bind-Value="inputData.CPSNumber" Placeholder="CPS Number" Style="width: 100%; text-align: left" @oninput="() => AddNewCard(index)" />
            </div>
            <div class="form-group">
                <RadzenAutoComplete TextProperty="Description" Data="@surplusItemsNews" @bind-Value="inputData.Description" Placeholder="Item Description" Style="width: 100%" />
            </div>
            <div class="form-group">
            </div>
            <RadzenDropDown TValue="string" ValueProperty="ReasonDescription" TextProperty="ReasonDescription" Data="@reasons" @bind-Value="inputData.ReasonDescription" Placeholder="Reason For Surplus" Style="width: 100%" />
        </RadzenCard>
    </div>
}



            </div>
        </div>
    </div>
</div>
</div>
<RadzenButton Style="width: 100%; min-width: 0px; min-height: 0px; height: 40px; display: block; background-color: #005570" Text="Review" Click="FinalizeList"></RadzenButton>



@code {
    [Inject]
    protected Surplus.WASPOService WASPOService { get; set; }

    protected System.Linq.IQueryable<Surplus.Models.WASPO.SurplusItemsNew> surplusItemsNews;
    protected System.Linq.IQueryable<Surplus.Models.WASPO.BuildingList> buildingLists;
    protected System.Linq.IQueryable<Surplus.Models.WASPO.Reason> reasons;

    // Create a class to hold form data
    public class FormData
    {
        public string BuildingName { get; set; }
        public string CPSNumber { get; set; }
        public string Description { get; set; }
        public string ReasonDescription { get; set; }
    }
    

    // Inject the ILocalStorageService
    [Inject]
    private ILocalStorageService localStorage { get; set; }

    // Create a list to store instances of the form data
    List<FormData> inputCardData;

    

    // Constructor to initialize inputCardData
    public Form()
    {
        inputCardData = new List<FormData>();
        // Add a default instance to the list
        inputCardData.Add(new FormData());
    }










    private int cardIdCounter = 1;

    [Inject]
    protected SecurityService Security { get; set; } // Counter for the card ID

// Event handler for adding a new card when a ReasonDescription is selected
private void AddNewCard(int index)
{
    // Check if the current card is the last one in the list
    if (index == inputCardData.Count - 1)
    {
        // Add a new FormData instance without copying the BuildingName property
        inputCardData.Add(new FormData
        {   
            CPSNumber = inputCardData[index].CPSNumber,
            Description = inputCardData[index].Description,
            ReasonDescription = inputCardData[index].ReasonDescription
            
        });

        cardIdCounter++; // Increase the ID counter
    }
}


    // Function to save inputCardData to local storage
    private async Task SaveInputCardDataToLocalStorage()
    {
        try
        {
            await localStorage.SetItemAsync("inputCardData", inputCardData);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error saving to local storage: {ex.Message}");
        }
    }

    // Function to load inputCardData from local storage
    private async Task LoadInputCardDataFromLocalStorage()
    {
        try
        {
            inputCardData = await localStorage.GetItemAsync<List<FormData>>("inputCardData");
            // Initialize the list if it's null
            if (inputCardData == null)
            {
                inputCardData = new List<FormData>();
                //Initialize list with Default instance of FormData when nothing is stored in local storage
                FormData defaultFormData = new FormData();
                inputCardData.Add(defaultFormData);

            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error loading from local storage: {ex.Message}");
        }
    }

    protected override async Task OnInitializedAsync()
    {
        surplusItemsNews = await WASPOService.GetSurplusItemsNews();
        buildingLists = await WASPOService.GetBuildingLists();
        reasons = await WASPOService.GetReasons();

        // Load inputCardData from local storage when the page initializes
        await LoadInputCardDataFromLocalStorage();
    }

    // Event handler for the "Finalize List" button
// Event handler for the "Finalize List" button

    // Event handler for the "Finalize List" button
    private async Task FinalizeList()
    {
        // Save the input card data to local storage
        await SaveInputCardDataToLocalStorage();

        // Navigate to the summary page
        NavManager.NavigateTo("/summary");
    }

} 

EDIT Here is my UPDATED summary code:

   @page "/summary"
@attribute [Authorize]
@using System.Text.Json;
@inject IJSRuntime _js
@inject NavigationManager NavigationManager
@inject DialogService DialogService
@inject ContextMenuService ContextMenuService
@inject TooltipService TooltipService
@inject NotificationService NotificationService
@inject IJSRuntime JSRuntime
@using Blazored.LocalStorage
@inject CSPNumerManager CspNumberService

<PageTitle>Summary</PageTitle>

<RadzenGrid Data="@filteredSessionData" TItem="FormData">

    <Columns>
        <RadzenGridColumn TItem="FormData" Property="BuildingName" Title="School" />
        <RadzenGridColumn TItem="FormData" Property="CPSNumber" Title="CPS Number" />
        <RadzenGridColumn TItem="FormData" Property="Description" Title="Item Description" />
        <RadzenGridColumn TItem="FormData" Property="ReasonDescription" Title="Reason For Surplus" />
        <RadzenGridColumn TItem="FormData" Property="Edit">
            <Template Context="data">
                <div style="text-align: center;">
                    <RadzenButton Icon="edit" Style="background-color: #005570;" Click="() => EditItem(data)"></RadzenButton>
                </div>
            </Template>
        </RadzenGridColumn>
    </Columns>
</RadzenGrid>
<RadzenButton Style="width: 100%; min-width: 0px; min-height: 0px; height: 40px; display: block; background-color: #005570" Text="Submit" @onclick="async () => await YourSaveFunction()"></RadzenButton>
<div class="back-button">
    <RadzenButton Icon="print" Style="background-color: #005570; float: left; vertical-align: text-top; text-align: center" Click="PrintMe"></RadzenButton>
</div>

@code {





    private async Task PrintMe()
        => await _js.InvokeVoidAsync("window.print");
    // Define the FormData class if it's not already defined
    public class FormData
    {
        public string BuildingName { get; set; }
        public string CPSNumber { get; set; }
        public string Description { get; set; }
        public string ReasonDescription { get; set; }
        public int SurplusStatus { get; set; }
    }



    // Read the stored local storage data
    private List<FormData> sessionData = new List<FormData>();

    // Filtered session data without null CPSNumber
    private List<FormData> filteredSessionData = new List<FormData>();

    // Inject the ILocalStorageService
    [Inject]
    private ILocalStorageService localStorage { get; set; }

    [Inject]
    protected SecurityService Security { get; set; }

    protected override async Task OnInitializedAsync()
    {
        // Load the data from local storage
        try
        {
            sessionData = await localStorage.GetItemAsync<List<FormData>>("inputCardData");

            // Filter out rows with null CPSNumber
            filteredSessionData = sessionData.Where(row => !string.IsNullOrEmpty(row.CPSNumber)).ToList();

            // Set the same BuildingName for all rows
            if (filteredSessionData.Count > 0)
            {
                var buildingName = filteredSessionData[0].BuildingName;
                filteredSessionData.ForEach(row => row.BuildingName = buildingName);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error loading from local storage: {ex.Message}");
        }
    }

    private void EditItem(FormData data)
    {
        NavigationManager.NavigateTo("/Form"); // Replace with the actual Razor page URL
    }


    private async Task YourSaveFunction()
    {
        await CspNumberService.SaveUserData();

        // Display a success message or perform other actions
        NotificationService.Notify(NotificationSeverity.Success, "Data submitted successfully");
    }


}




<style>
    .back-button {
        position: fixed;
        bottom: 20px;
        right: 20px;
    }

    .back-button a {
        display: inline-block;
        background-color: #005570;
        color: white;
        border-radius: 50%;
        width: 40px;
        height: 40px;
        line-height: 40px;
        text-align: center;
        text-decoration: none;
    }

    .back-icon {
        font-size: 20px;
    }
</style>

EDIT Here is my CSPNumerManager class code:

using System.Threading.Tasks;
using Blazored.LocalStorage;
using Surplus;
using Surplus.Data;
using static Surplus.Pages.Summary;

public class CSPNumerManager
{
    private readonly WASPOService _waspoService;
    private readonly ILocalStorageService _localStorage;

    public CSPNumerManager(WASPOService waspoService, ILocalStorageService localStorage)
    {
        _waspoService = waspoService;
        _localStorage = localStorage;
    }

    public async Task<bool> SaveUserData()
    {
        var dataToSend = await _localStorage.GetItemAsync<List<FormData>>("inputCardData");

        // Check each CPSNumber against the database
        foreach (var formData in dataToSend)
        {
            _waspoService.UpdateOrAddFormData(new List<FormData> { formData });
        }

        return true; // You might want to enhance this logic based on your requirements
    }
}

here is my service code Please look at the PLEASE LOOK HERE COMMENT IN THIS CODE:

using System;
using System.Data;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Components;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Radzen;

using Surplus.Data;
using static Surplus.Pages.Summary;
using Surplus.Pages;

namespace Surplus
{
    public partial class WASPOService
    {
        WASPOContext Context
        {
            get
            {
                return this.context;
            }
        }

        private readonly WASPOContext context;
        private readonly NavigationManager navigationManager;

        public WASPOService(WASPOContext context, NavigationManager navigationManager)
        {
            this.context = context;
            this.navigationManager = navigationManager;
        }

        public void Reset() => Context.ChangeTracker.Entries().Where(e => e.Entity != null).ToList().ForEach(e => e.State = EntityState.Detached);

        public void ApplyQuery<T>(ref IQueryable<T> items, Query query = null)
        {
            if (query != null)
            {
                if (!string.IsNullOrEmpty(query.Filter))
                {
                    if (query.FilterParameters != null)
                    {
                        items = items.Where(query.Filter, query.FilterParameters);
                    }
                    else
                    {
                        items = items.Where(query.Filter);
                    }
                }

                if (!string.IsNullOrEmpty(query.OrderBy))
                {
                    items = items.OrderBy(query.OrderBy);
                }

                if (query.Skip.HasValue)
                {
                    items = items.Skip(query.Skip.Value);
                }

                if (query.Top.HasValue)
                {
                    items = items.Take(query.Top.Value);
                }
            }
        }

        public async Task ExportReasonsToExcel(Query query = null, string fileName = null)
        {
            navigationManager.NavigateTo(query != null ? query.ToUrl($"export/waspo/reasons/excel(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')") : $"export/waspo/reasons/excel(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')", true);
        }

        public async Task ExportReasonsToCSV(Query query = null, string fileName = null)
        {
            navigationManager.NavigateTo(query != null ? query.ToUrl($"export/waspo/reasons/csv(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')") : $"export/waspo/reasons/csv(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')", true);
        }

        partial void OnReasonsRead(ref IQueryable<Surplus.Models.WASPO.Reason> items);

        public async Task<IQueryable<Surplus.Models.WASPO.Reason>> GetReasons(Query query = null)
        {
            var items = Context.Reasons.AsQueryable();

            if (query != null)
            {
                if (!string.IsNullOrEmpty(query.Expand))
                {
                    var propertiesToExpand = query.Expand.Split(',');
                    foreach (var p in propertiesToExpand)
                    {
                        items = items.Include(p.Trim());
                    }
                }

                ApplyQuery(ref items, query);
            }

            OnReasonsRead(ref items);

            return await Task.FromResult(items);
        }

        public async Task ExportBuildingListsToExcel(Query query = null, string fileName = null)
        {
            navigationManager.NavigateTo(query != null ? query.ToUrl($"export/waspo/buildinglists/excel(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')") : $"export/waspo/buildinglists/excel(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')", true);
        }

        public async Task ExportBuildingListsToCSV(Query query = null, string fileName = null)
        {
            navigationManager.NavigateTo(query != null ? query.ToUrl($"export/waspo/buildinglists/csv(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')") : $"export/waspo/buildinglists/csv(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')", true);
        }

        partial void OnBuildingListsRead(ref IQueryable<Surplus.Models.WASPO.BuildingList> items);

        public async Task<IQueryable<Surplus.Models.WASPO.BuildingList>> GetBuildingLists(Query query = null)
        {
            var items = Context.BuildingLists.AsQueryable();

            if (query != null)
            {
                if (!string.IsNullOrEmpty(query.Expand))
                {
                    var propertiesToExpand = query.Expand.Split(',');
                    foreach (var p in propertiesToExpand)
                    {
                        items = items.Include(p.Trim());
                    }
                }

                ApplyQuery(ref items, query);
            }

            OnBuildingListsRead(ref items);

            return await Task.FromResult(items);
        }

        public async Task ExportSurplusItemsNewsToExcel(Query query = null, string fileName = null)
        {
            navigationManager.NavigateTo(query != null ? query.ToUrl($"export/waspo/surplusitemsnews/excel(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')") : $"export/waspo/surplusitemsnews/excel(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')", true);
        }

        public async Task ExportSurplusItemsNewsToCSV(Query query = null, string fileName = null)
        {
            navigationManager.NavigateTo(query != null ? query.ToUrl($"export/waspo/surplusitemsnews/csv(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')") : $"export/waspo/surplusitemsnews/csv(fileName='{(!string.IsNullOrEmpty(fileName) ? UrlEncoder.Default.Encode(fileName) : "Export")}')", true);
        }

        partial void OnSurplusItemsNewsRead(ref IQueryable<Surplus.Models.WASPO.SurplusItemsNew> items);

        public async Task<IQueryable<Surplus.Models.WASPO.SurplusItemsNew>> GetSurplusItemsNews(Query query = null)
        {
            var items = Context.SurplusItemsNews.AsQueryable();

            if (query != null)
            {
                if (!string.IsNullOrEmpty(query.Expand))
                {
                    var propertiesToExpand = query.Expand.Split(',');
                    foreach (var p in propertiesToExpand)
                    {
                        items = items.Include(p.Trim());
                    }
                }

                ApplyQuery(ref items, query);
            }

            OnSurplusItemsNewsRead(ref items);

            return await Task.FromResult(items);
        }



        // New method for updating or adding FormData

        //PLEASE LOOK AT THIS CODE HERE!!!!
        public void UpdateOrAddFormData(List<FormData> formDataList)
        {
            foreach (var formData in formDataList)
            {
                // Issue with AsQueryable
                var existingData = ((IEnumerable<FormData>)Context.FormData).AsQueryable().FirstOrDefault(fd => fd.CPSNumber == formData.CPSNumber);

                if (existingData != null)
                {
                    // Update existing row in the database
                    existingData.BuildingName = formData.BuildingName;
                    existingData.Description = formData.Description;
                    existingData.ReasonDescription = formData.ReasonDescription;
                    existingData.SurplusStatus = 1; // Set SurplusStatus to 1
                }
                else
                {
                    // Issue with ToList
                    var formDataListCollection = ((IEnumerable<FormData>)Context.FormData).ToList();

                    // Add new row to the database
                    formData.SurplusStatus = 1; // Set SurplusStatus to 1 for new row
                    formDataListCollection.Add(formData);
                }
            }

            Context.SaveChanges();
        }
    }
}

However when I click the submit button the data in formlist is not being inserted/updated in the database

UPDATE: what I see when I click the submit button in the debugger:

info: Microsoft.Hosting.Lifetime[0]
 Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Development
    info: Microsoft.Hosting.Lifetime[0]
          Content root path: D:\Apps\CPS Surplus\CPS Surplus\
    info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
          Entity Framework Core 6.0.0 initialized 'WASPOContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer:6.0.0' with options: None
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (11ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
          SELECT [b].[BuildingID], [b].[BuildingLink], [b].[BuildingName], [b].[BuildingSubFlag], [b].[DESENo], [b].[LocCode]
          FROM [dbo].[BuildingList] AS [b]
    info: Microsoft.EntityFrameworkCore.Database.Command[20101]
          Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
    

Thank you in advance!


Solution

  • so the issue was in my submit button code it needed to be

    <RadzenButton Style="width: 100%; min-width: 0px; min-height: 0px; height: 40px; display: block; background-color: #005570" Text="Submit"  Click="YourSaveFunction"></RadzenButton>
    

    NOT

    <RadzenButton Style="width: 100%; min-width: 0px; min-height: 0px; height: 40px; display: block; background-color: #005570" Text="Submit" @onclick="async () => await YourSaveFunction()"></RadzenButton>
    

    The button was never registering the click..... changed that and it worked like a charm!!!!!!!!!!!

    thank you to everyone who helped out on this post!