CHAPTER 6
In this final chapter, we will construct a search page that will allow the user to search for store locations and display those store locations on an interactive map.
As the first step, we will add a method to the store location service that will accept the latitude and longitude coordinates and return an address. This is called reverse geolocation. To perform this, we will call the Azure Maps API. The API method we will call will return a ReverseSearchAddressResult object.
Later, the return result from this method will be consumed by the store search page to display the address of the user’s current location.
As we did with SearchAddressResult in the previous chapter, the first step is to create a class, matching the properties of the ReverseSearchAddressResult object.

Figure 63: Create SearchAddressResultReverse.cs
In the Models folder, add a SearchAddressResultReverse.cs class using the following code.
Code Listing 52: SearchAddressResultReverse.cs
#nullable disable namespace BlazorStoreFinder.Reverse {
} |
In your web browser, navigate to the following URL:
https://docs.microsoft.com/en-us/rest/api/maps/search/get-search-address-reverse

Figure 64: Copy ReverseSearchAddressResult
Scroll down to the Sample Response section and click Copy.
In Visual Studio, select Edit from the toolbar, then Paste Special > Paste JSON As Classes to paste the contents inside the namespace declaration.
The pasted code will set that root class name to Rootobject. Change Rootobject to SearchAddressResultReverse.
Next, add the following using statement to the store location service class (StoreLocationService.cs).
Code Listing 53: BlazorStoreFinder.Reverse Using Statement
using BlazorStoreFinder.Reverse; |
Finally, add the following method to the class that will call the Azure Maps API and return an address when passed latitude and longitude coordinates.
Code Listing 54: GeocodeReverse
public async Task<SearchAddressResultReverse> GeocodeReverse(Coordinate paramCoordinate) { SearchAddressResultReverse result = new SearchAddressResultReverse(); // Create a HTTP client to make the REST call. // Search - Get search address reverse // https://bit.ly/3Nuz5cP using (var client = new System.Net.Http.HttpClient()) { // Get an access token from AuthService. var AccessToken = await AuthService.GetAccessToken(); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); // Pass the Azure Maps client ID. client.DefaultRequestHeaders.Add( "x-ms-client-id", AuthService.ClientId);
// Pass the access token in the auth header. client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( "Bearer", AccessToken); // Build the URL. StringBuilder sb = new StringBuilder(); // Request an address search. sb.Append( "https://atlas.microsoft.com/search/address/reverse/json?"); // Specify the API version and language. sb.Append("api-version=1.0"); // Pass latitude. sb.Append($"&query={paramCoordinate.X}"); // Pass longitude. sb.Append($",{paramCoordinate.Y}"); // Set the URL. var url = new Uri(sb.ToString()); // Call Azure Maps and get the response. var Response = await client.GetAsync(url); // Read the response. var responseContent = await Response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<SearchAddressResultReverse>( responseContent); } return result; } |
The final method we will add to the store location service will accept latitude and longitude coordinates, search the database of store locations, and return the nearest locations and their distances from the search location.

Figure 65: Create StoreSearchResult.cs
In the Models folder, add a new class called StoreSearchResult.cs using the following code.
Code Listing 55: StoreSearchResult
namespace BlazorStoreFinder { public class StoreSearchResult { public string? LocationName { get; set; } public string? LocationAddress { get; set; } public double LocationLatitude { get; set; } public double LocationLongitude { get; set; } public double Distance { get; set; } } } |
Now, to allow the search to be performed, add the following code to the store location service class (StoreLocationService.cs).
Code Listing 56: GetNearbyStoreLocations
public List<StoreSearchResult> GetNearbyStoreLocations( Coordinate paramCoordinate) { List<StoreSearchResult> colStoreLocations = new List<StoreSearchResult>(); // Using a raw SQL query because sometimes NetTopologySuite // cannot properly translate a query. StringBuilder sb = new StringBuilder(); // Set distance to 25 miles. sb.Append("declare @Distance as int = 25 "); // Declare the coordinate. sb.Append($"declare @Latitude as nvarchar(250) = "); sb.Append($"'{paramCoordinate.Y}' "); sb.Append($"declare @Longitude as nvarchar(250) = "); sb.Append($"'{paramCoordinate.X}' "); // Declare the geography. sb.Append("declare @location sys.geography "); sb.Append(" "); // Set the geography to the coordinate. sb.Append("set @location = "); sb.Append("geography::STPointFromText('POINT"); sb.Append("(' + @Longitude + ' ' + @Latitude + ')', 4326) "); sb.Append(" "); // Search for store locations within the distance. sb.Append("SELECT "); sb.Append("[LocationName], "); sb.Append("[LocationAddress], "); sb.Append("[LocationLatitude], "); sb.Append("[LocationLongitude], "); sb.Append("[LocationData].STDistance(@location) "); sb.Append("/ 1609.3440000000001E0 AS [DistanceInMiles] "); sb.Append("FROM [StoreLocations] "); sb.Append("where [LocationData].STDistance(@location) "); sb.Append("/ 1609.3440000000001E0 < @Distance "); sb.Append("order by [LocationData].STDistance(@location) "); sb.Append("/ 1609.3440000000001E0 ");
using (SqlConnection connection = new SqlConnection(_context.Database.GetConnectionString())) { SqlCommand command = new SqlCommand(sb.ToString(), connection); connection.Open(); SqlDataReader reader = command.ExecuteReader(); while (reader.Read()) { colStoreLocations.Add(new StoreSearchResult { LocationName = reader["LocationName"].ToString(),
LocationAddress = reader["LocationAddress"].ToString(),
LocationLatitude = Convert.ToDouble(reader["LocationLatitude"]),
LocationLongitude = Convert.ToDouble(reader["LocationLongitude"]),
Distance = double.Parse(reader["DistanceInMiles"].ToString()) }); } reader.Close(); } return colStoreLocations; } |

Figure 66: Index.razor
We will construct the store search page on the existing Index.razor page.
Replace all the existing code with the following code.
Code Listing 57: Index Control
@page "/" @using BlazorStoreFinder @using NetTopologySuite.Geometries @using Syncfusion.Blazor.Layouts @using Syncfusion.Blazor.Buttons @using Syncfusion.Blazor.Inputs @using Syncfusion.Blazor.SplitButtons @using Syncfusion.Blazor.Lists @using Darnton.Blazor.DeviceInterop.Geolocation; @using AzureMapsControl.Components.Map @inject IGeolocationService GeolocationService @inject StoreLocationService _StoreLocationService @inherits OwningComponentBase<StoreLocationService> <h4>Blazor Store Finder</h4> @code { MapEventArgs? myMap; SfListView<StoreSearchResult>? StoreSearchResultListBox; protected GeolocationResult? CurrentPositionResult { get; set; }
List<StoreSearchResult> colStoreLocations = new List<StoreSearchResult>();
Coordinate CurrentCoordinate = new Coordinate(); string CurrentLocation = ""; bool searching = false; } |
Add the following code to the UI section. This code uses a Syncfusion TextBox component to allow the user to enter an address to use for the search. It also uses a Syncfusion ListView component to display a list of the store locations that are the result of the search.
Finally, it uses an AzureMap control to display the stores on an interactive map.
Code Listing 58: Search Results and Azure Map
<div class="row" style="width:1500px"> <div class="col-xs-2 col-sm-2 col-lg-2 col-md-2"> <div class="row"> <div class="col-xs-9 col-sm-9 col-lg-9 col-md-9" style="margin-top:10px;"> <SfTextBox Placeholder="Location" @bind-Value="CurrentLocation"></SfTextBox> </div> <div class="col-xs-3 col-sm-3 col-lg-3 col-md-3"> @if (searching) { <div class="spinner-border text-primary" role="status" style="margin-top:8px;"></div> } else { <button class="e-control e-btn e-lib" @onclick="Search" style="width:auto; margin-top:8px;"> <span class="oi oi-magnifying-glass"></span> </button> } </div> </div> <br /> @if (colStoreLocations.Count > 0) { <SfListView @ref="StoreSearchResultListBox" DataSource="@colStoreLocations" Height="450px" CssClass="e-list-template listview-template"> <ListViewFieldSettings TValue="StoreSearchResult" Id="LocationName" Text="Text" Child="Child"></ListViewFieldSettings> <ListViewTemplates TValue="StoreSearchResult"> <Template> @{ StoreSearchResult currentData = (StoreSearchResult)context; <div class="e-list-wrapper e-list-avatar e-list-multi-line"> <span class="e-avatar"><span class="oi oi-globe"></span> </span> <span class="e-list-item-header"> @currentData.LocationName</span> <span class="e-list-item-header"> @currentData.LocationAddress</span> <span class="e-list-content"> @currentData.Distance.ToString("F") miles </span> </div> } </Template> </ListViewTemplates> </SfListView> } </div> <div class="col-xs-10 col-sm-10 col-lg-10 col-md-10"> <AzureMap Id="map" CameraOptions="new CameraOptions { Zoom = 10 }" StyleOptions= "new StyleOptions { ShowLogo = false, ShowFeedbackLink = false }" EventActivationFlags= "MapEventActivationFlags.None().Enable(MapEventType.Ready)" OnReady="OnMapReadyAsync" /> </div> </div> |
Add the following method to the code section. This retrieves the user’s current latitude and longitude. It will then pass it to the ReverseGeocode method created earlier. The address returned will be set as the default search address, and the Search method (to be implemented later) will perform a search of stores in the database near that address.
Code Listing 59: OnAfterRenderAsync
protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { // Get current location // will cause a popup to show to ask permission. CurrentPositionResult = await GeolocationService.GetCurrentPosition(); if (CurrentPositionResult.IsSuccess) { // Get latitude and longitude. string? CurrentLatitude = CurrentPositionResult.Position?.Coords?.Latitude.ToString("F2"); string? CurrentLongitude = CurrentPositionResult.Position?.Coords?.Longitude.ToString("F2"); // Set latitude and longitude // (to be consumed by GetTile() method) if (CurrentLatitude != null && CurrentLongitude != null) { CurrentCoordinate.X = Convert.ToDouble(CurrentLatitude); CurrentCoordinate.Y = Convert.ToDouble(CurrentLongitude); // Reverse geocode coordinate and set location. var SearchAddressResult = await Service.GeocodeReverse(CurrentCoordinate); if (SearchAddressResult.addresses[0].address.freeformAddress != null) { CurrentLocation = SearchAddressResult.addresses[0].address.freeformAddress; // Search for nearby stores. await Search(); } } StateHasChanged(); } } } |
Next, add the following method that will run when the AzureMap control fires its OnReady event. This allows programmatic access to the AzureMaps control through the myMap variable.
Code Listing 60: OnMapReadyAsync
public async Task OnMapReadyAsync(MapEventArgs eventArgs) { await Task.Run(() => myMap = eventArgs); } |
Finally, add the following method that will search for store locations in the database and populate the colStoreLocations collection that lists results on the page. This will also show a map marker for the current location and icons for each store location on the AzureMap control.
Code Listing 61: Search Store Locations
public async Task Search() { searching = true; StateHasChanged(); // Clear location results. colStoreLocations = new List<StoreSearchResult>(); // Geocode address. Coordinate CurrentCoordinate = await Service.GeocodeAddress(CurrentLocation); if (CurrentCoordinate != null) { // Find nearby stores. colStoreLocations = Service.GetNearbyStoreLocations(CurrentCoordinate); } searching = false; StateHasChanged(); if (myMap != null && CurrentCoordinate != null) { // Center map to current location. await myMap.Map.SetCameraOptionsAsync( options => options.Center = new AzureMapsControl.Components.Atlas.Position (CurrentCoordinate.X, CurrentCoordinate.Y)); // Add icon for current location. await myMap.Map.ClearHtmlMarkersAsync(); var HomeIcon = new AzureMapsControl.Components.Markers.HtmlMarker( new AzureMapsControl.Components.Markers.HtmlMarkerOptions { Position = new AzureMapsControl.Components.Atlas .Position( CurrentCoordinate.X, CurrentCoordinate.Y ), Draggable = false, Color = "#FF0000" }); await myMap.Map.AddHtmlMarkersAsync(HomeIcon); // Add icons for search results. foreach (var store in colStoreLocations) { var StoreIcon = new AzureMapsControl.Components.Markers.HtmlMarker( new AzureMapsControl.Components.Markers.HtmlMarkerOptions { Position = new AzureMapsControl.Components.Atlas .Position( store.LocationLongitude, store.LocationLatitude ), Draggable = false, Color = "#0000FF" }); await myMap.Map.AddHtmlMarkersAsync(StoreIcon); } } } |
Run the application and navigate to the Home page.

Figure 67: Search for Store Locations
The search box will be populated with a location that matches your current location. The map will display, centered at the current location with a red icon. Blue icons will display for each nearby store location from the database.
In the final example, to demonstrate how we can dynamically interact with the AzureMaps control, we will center the map on a store location when it is selected in the search result list.
In the UI, change the following line of code.
Code Listing 62: Update Div
<div class="e-list-wrapper e-list-avatar e-list-multi-line"> |
To the following code, add an onclick event handler that will call the OnStoreSelect method (to be created later), passing the currently selected store location.
Code Listing 63: Add Onclick Handler to Div
<div class="e-list-wrapper e-list-avatar e-list-multi-line" @onclick="(e => OnStoreSelect(currentData))"> |
Finally, add the following OnStoreSelect method that will center the AzureMaps control on the selected icon.
Code Listing 64: OnStoreSelect
async Task OnStoreSelect(StoreSearchResult SearchResult) { // Center map on store. if (myMap != null && CurrentCoordinate != null) { await myMap.Map.SetCameraOptionsAsync( options => options.Center = new AzureMapsControl.Components.Atlas.Position (SearchResult.LocationLongitude, SearchResult.LocationLatitude)); } } |

Figure 68: Center Map on Selected Store Location
Run the application.
When you click a store location in the search results on the left-hand side of the page, the map will center on that location.
We've now seen how Azure Maps, together with Syncfusion controls, enables you to create sophisticated applications using Blazor. Try it for yourself.