Using BaseHttpService
as a foundation that abstracts away the HTTP request details, we can now begin to create services that leverage it to get responses back from the API in the form of domain-specific models. Specifically, we will create a data service that can be used by the ViewModels to get the TripLogEntry
objects from the backend service.
We will start off by defining an interface for the data service that can be injected into the ViewModels, ensuring there is no strict dependency on the API or the logic that communicates with it, continuing the pattern we put in place in Chapter 4, Platform Specific Services and Dependency Injection.
ITripLogDataService
in the Services
folder in the core library:public interface ITripLogDataService { }
ITripLogDataService
interface with methods to get, update, and delete TripLogEntry
objects:public interface ITripLogDataService { Task<IList<TripLogEntry>> GetEntriesAsync (); Task<TripLogEntry> GetEntryAsync (string id); Task<TripLogEntry> AddEntryAsync (TripLogEntry entry); Task<TripLogEntry> UpdateEntryAsync (TripLogEntry entry); Task RemoveEntryAsync (TripLogEntry entry); }
Next, we need to create an implementation of this interface that will also subclass BaseHttpService
so that it has access to our HttpClient
implementation.
TripLogApiDataService
in the Services
folder in the core library:public class TripLogApiDataService : BaseHttpService, ITripLogDataService { }
TripLogApiDataService
class: a Uri
and an IDictionary<string, string>
, to store the base URL and headers respectively, to be used for all requests:public class TripLogApiDataService : BaseHttpService, ITripLogDataService { readonly Uri _baseUri; readonly IDictionary<string, string> _headers; }
TripLogApiDataService
constructor to take in a Uri
parameter and set the private _baseUri
and _headers
properties:public class TripLogApiDataService : BaseHttpService, ITripLogDataService { readonly Uri _baseUri; readonly IDictionary<string, string> _headers; public TripLogApiDataService (Uri baseUri) { _baseUri = baseUri; _headers = new Dictionary<string, string> (); // TODO: Add header with Authentication-based token in chapter 7 _headers.Add ("zumo-api-version", "2.0.0"); } }
ITripLogDataService
using the SendRequestAsync<T>
base class method:public class TripLogApiDataService : BaseHttpService, ITripLogDataService { readonly Uri _baseUri; readonly IDictionary<string, string> _headers; // ... #region ITripLogDataService implementation public async Task<IList<TripLogEntry>> GetEntriesAsync () { var url = new Uri (_baseUri, "/tables/entry"); var response = await SendRequestAsync<TripLogEntry[]> (url, HttpMethod.Get, _headers); return response; } public async Task<TripLogEntry> GetEntryAsync (string id) { var url = new Uri (_baseUri, string.Format ("/tables/entry/{0}", id)); var response = await SendRequestAsync<TripLogEntry> (url, HttpMethod.Get, _headers); return response; } public async Task<TripLogEntry> AddEntryAsync (TripLogEntry entry) { var url = new Uri (_baseUri, "/tables/entry"); var response = await SendRequestAsync<TripLogEntry> (url, HttpMethod.Post, _headers, entry); return response; } public async Task<TripLogEntry> UpdateEntryAsync (TripLogEntry entry) { var url = new Uri (_baseUri, string.Format ("/tables/entry/{0}", entry.Id)); var response = await SendRequestAsync<TripLogEntry>(url, new HttpMethod("PATCH"), _headers, entry); return response; } public async Task RemoveEntryAsync (TripLogEntry entry) { var url = new Uri (_baseUri, string.Format ("/tables/entry/{0}", entry.Id)); var response = await SendRequestAsync<TripLogEntry>(url, HttpMethod.Delete, _headers); } #endregion }
Each method in this TripLog service calls the SendRequestAsync
method on the base class passing in the API route, the appropriate HttpMethod
, and the zumo-api-version
header that we used in the first section. The Add
and Update
methods also pass in a TripLogEntry
object that will be serialized and added to the HTTP request message content. In the next chapter, we will implement authentication with the API and update this service to pass in an authentication based token in the header.
Using the API and data service we created, we can now update the ViewModels in the app to use live data instead of the local, hard-coded data they currently use. We will continue to leverage the patterns we put in place in previous chapters to ensure our ViewModels remain testable and do not have any specific dependencies on the Azure API or even the HTTP communication logic.
TripLogCoreModule
in the core library to register our ITripLogDataService
implementation into the IoC:public class TripLogCoreModule : NinjectModule { public override void Load () { // ViewModels Bind<MainViewModel> ().ToSelf (); Bind<DetailViewModel> ().ToSelf (); Bind<NewEntryViewModel> ().ToSelf (); // Core Services var tripLogService = new TripLogApiDataService (new Uri("https://<your-service-name>.azurewebsites.net")); Bind<ITripLogDataService> () .ToMethod (x => tripLogService) .InSingletonScope (); } }
MainViewModel
constructor to take an ITripLogDataService
parameter, which will be provided automatically via dependency injection:readonly ITripLogDataService _tripLogService; public MainViewModel ( INavService navService, ITripLogDataService tripLogService) : base (navService) { _tripLogService = tripLogService; LogEntries = new ObservableCollection<TripLogEntry> (); }
LoadEntries
method in MainViewModel
, replacing the three second delay and hard-coded data population with a call to the live TripLog API via the current ITripLogDataService
that was injected into the ViewModel's constructor:async Task LoadEntries() { if (IsBusy) return; IsBusy = true; try { var entries = await _tripLogService.GetEntriesAsync (); LogEntries = new ObservableCollection<TripLogEntry> (entries); } finally { IsBusy = false; } }
No other changes to MainViewModel
are required. Now, when the app is launched, instead of the hard-coded data loading, you will see the items stored in the Azure backend service database.
Now, we need to update the NewEntryViewModel
so that when we add a new entry, it is actually saved to the Azure backend through the data service.
NewEntryViewModel
constructor to take an ITripLogDataService
parameter:readonly ITripLogDataService _tripLogService; public NewEntryViewModel ( INavService navService, ILocationService locService, ITripLogDataService tripLogService) : base (navService) { _locService = locService; _tripLogService = tripLogService; Date = DateTime.Today; Rating = 1; }
Action
method to call the AddEntryAsync
method on the data service:async Task ExecuteSaveCommand() { if (IsBusy) return; IsBusy = true; var newItem = new TripLogEntry { Title = this.Title, Latitude = this.Latitude, Longitude = this.Longitude, Date = this.Date, Rating = this.Rating, Notes = this.Notes }; try { await _tripLogService.AddEntryAsync (newItem); await NavService.GoBack (); } finally { IsBusy = false; } }
Now, if we launch the app, navigate to the new entry page, fill out the form, and click Save, the log entry will be sent to the TripLog backend service and saved in the database.
3.139.239.41