Countries & Cities - Part 4
Editing Cities
The method we will be adopting for editing cities is similar to the one used for adding cities; we will add an 'Edit' button that will open a separate 'Edit a City' dialog, having first established which record from the city grid the user has selected. When the 'Save' button in the edit dialog is clicked the system will check for duplicates and either issue a warning (via another dialog), or save the edit.
Add the following to the HTML section after the existing HTML for the 'Add' button :
<SfButton CssClass="e-small e-success" @onclick="EditCity">
Edit a City
</SfButton>To add the dialog to enable editing cities, insert the following immediately after SfDialog code for adding a city.
<SfDialog @ref="DialogEditCity" IsModal="true" Width="500px" ShowCloseIcon="true" Visible="false">
<EditForm Model="@editCities" OnValidSubmit="@CitiesSaveEdit">
<div>
<SfTextBox Enabled="true" Placeholder="City"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="editCities.CityName"></SfTextBox>
<SfNumericTextBox Enabled="true" Placeholder="Population" Width="50"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="editCities.CityPopulation"></SfNumericTextBox>
</div>
<br /><br />
<div class="e-footer-content">
<div class="button-container">
<button type="submit" class="e-btn e-normal e-primary">Save</button>
</div>
</div>
</EditForm>
</SfDialog>The above code for the 'DialogEditCity' is very similar to the 'DialogAddCity', except that I have chosen to use a separate model (editCities), a separate OnValidSubmit action and binding to the new model. There may be a way to combine the add and edit dialogs, but for simplicity I have chosen to keep them separate. It also shows the modular way that a page can be constructed.
Checking for duplicate cities
We are again faced with the problem of preventing users entering the same city twice for the same country, and again we will be initially tackling the problem at SQL level.
SQL
Use the following code to create a new stored procedure called spCities_UpdateWithDuplicateChecking. This follows the same pattern used for adding cities with duplicate checking.
USE [CountriesDb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-------------- Stored Proc for UPDATE
CREATE PROCEDURE [dbo].[spCities_UpdateWithDuplicateChecking]
-- Parameters for Update stored procedure.
@CityId int,
@CityName varchar(50),
@CountryId int,
@CityPopulation int
AS
DECLARE @ResultValue int
BEGIN TRAN
IF EXISTS
(
SELECT * FROM Cities
WHERE (CityName = @CityName and CountryId = @CountryId) AND CityId <> @CityId
)
BEGIN
SET @ResultValue = 99
END
ELSE
BEGIN
UPDATE Cities SET CityName = @CityName, CountryId = @CountryId, CityPopulation = @CityPopulation WHERE CityId = @CityId
set @ResultValue = @@ERROR
END
IF @ResultValue <> 0
BEGIN
ROLLBACK TRAN
END
ELSE
BEGIN
COMMIT TRAN
END
RETURN @ResultValue
GOCitiesService
CitiesService.cs must be amended to add code to use the new update with duplicate checking. Add the following after the 'CitiesInsertWithDuplicateCheck' procedure.
public async Task<int> CitiesUpdateWithDuplicateCheck(string CityName, int CountryId, int CityPopulation, int CityId)
{
int Success = 0;
var parameters = new DynamicParameters();
parameters.Add("CityName", CityName, DbType.String);
parameters.Add("CountryId", CountryId, DbType.Int32);
parameters.Add("CityPopulation", CityPopulation, DbType.Int32);
parameters.Add("CityId", CityId, DbType.Int32);
parameters.Add("@ReturnValue", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
using (var conn = new SqlConnection(_configuration.Value))
{
await conn.ExecuteAsync("spCities_UpdateWithDuplicateChecking", parameters, commandType: CommandType.StoredProcedure);
Success = parameters.Get<int>("@ReturnValue");
}
return Success;
}iCitiesService
The final stage of these changes requires that we add the the new CitiesUpdateWithDuplicateCheck to the Cities Service interface. Insert the following into ICitiesSevice.cs, I suggest immediately after the insert with duplicate check.
Task<int> CitiesUpdateWithDuplicateCheck(string CityName, int CountryId, int CityPopulation, int CityId);
Warn the user
If a duplicate city would be created by editing an existing city, the user must be warned. Fortunately this can be accomplished using the same warning dialog already created for use when adding a city.
Checking a City has been selected
When the 'Edit' button is clicked we need to ensure that the user has selected a city to edit, and notify the user if one hasn't been selected. The check to ensure a city has been selected will be carried out as part of the 'EditCity' proceure, but we need to add a new dialog to notify the user that a city hasn't been selected. This is almost identical to the missing country warning, and possibly the two could be combined, but to keep things simple we will have a separate dialog. Copy and paste the following code in the HTML section to create the missing city dialog. (After the missing country dialog would seem a good spot.)
<SfDialog @ref="DialogMissingCity" IsModal="true" Width="250px" ShowCloseIcon="true" Visible="false">
<DialogTemplates>
<Header> Warning! </Header>
<Content> You must select a city from the grid.</Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="OK" IsPrimary="true" OnClick="@CloseDialogMissingCity" />
</DialogButtons>
</SfDialog>We also need to handle the OnClick event to close the dialog. Add the following procedure to handle this event. It could be added to the end of the code section.
private async Task CloseDialogMissingCity()
{
await this.DialogMissingCity.Hide();
}Getting details of the selected city
To pass the details of the city selected by the user, add the following to the HTML of the data-grid, before <SfGridColumns>
<GridEvents RowSelected="RowSelectHandler" TValue="Cities"></GridEvents>Changes to @Code
Changes are needed to the code section of CountriesAndCities.razor to handle:
- Declaring the SfDialog for 'DialogEditCity'
- Declaring the SfDialog for 'DialogMissingCity'
- Declare the editCities object
- Add the logic to obtain details of the city selected for editing
- Add the logic to open the dialog when the 'Edit a City' button is clicked
- Add the logic to handle the non-selection of a city
- Add the logic to save the city
- Add the logic to handle attempts to add a duplicate city
Insert the following in the existing declarations for SfDialogs
SfDialog DialogEditCity;
SfDialog DialogMissingCity;
Add the following after the existing declaration for 'Cities addCities':
Cities editCities = new Cities();To get the CityId of the city selected by the user, add the following procedure. I suggest after the 'OnChange' event for the drop-down list.
public void RowSelectHandler(RowSelectEventArgs<Cities> args)
{
//{args.Data} returns the current selected records.
SelectedCityId = args.Data.CityId;
}
A new variable was introduced by the the above procedure to record the ID of the selected city. This needs to be declared. Add it to the [Parameters] section.
public int SelectedCityId { get; set; } = 0;To open the edit city dialog add the following procedure. It checks that a city has been selected, and if not will open the missing city dialog, otherwise it populates the 'editCities' object with details of the selected city and opens the edit city dialog.
private async Task EditCity()
{
//Check that a City has been selected
if (SelectedCityId == 0)
{
await this.DialogMissingCity.Show();
}
else
{
//populate editCities (temporary data set used for the editing process)
editCities = await CitiesService.CitiesGetOne(SelectedCityId);
await this.DialogEditCity.Show();
}
}The user will be presented with a dialog with the city name and population ready for editing. Add the code to handle 'CitiesSaveEdit' for this dialog, as shown below:
protected async Task CitiesSaveEdit()
{
int Success = await CitiesService.CitiesUpdateWithDuplicateCheck(editCities.CityName, editCities.CountryId, editCities.CityPopulation, SelectedCityId);
if (Success != 0)
{
//City Name already exists
await this.DialogDuplicateCity.Show();
}
else
{
await this.DialogEditCity.Hide();
this.StateHasChanged();
editCities = new Cities();
}
cities = await CitiesService.Cities_GetByCountry(this.SelectedCountryId);
StateHasChanged();
}
This procedure will try to update the city, but if a duplicate would be created it opens the duplicate cities dialog. If the update is successful the edit city dialog is closed (hidden). In either case the editCities object is initialised and the cities object refreshed (for the currently selected country).
One final change is needed to the country drop-down list. If a user edits a city for one country and then changes to a different country and then tries to edit a city without actually selecting a city for the new country, the 'SelectedCityId' will still be set to the value from the first country. To avoid this happening add the following to the end of the 'OnChange' procedure so that 'SelectedCityID' always gets reset to 0 whenever a new country is selected:
SelectedCityId = 0; //If this isn't reset, editing a city for a new country could display an old record.Build an run the application.
Full Code
The full code for the changes made in this section can be found by clicking the above heading.



