As stated in previous posts the entire framework is built to be observable using Reactive Extensions (Rx).
Consider this as a simple scenario:
“Get me all known motorcycle parking places in Stockholm within a radius of 500 meters from my current location”
First things first – add the core references:
Next up, add a reference to the data library for Stockholm:
Now, for the ViewModel code parts..
from the top, a collection is needed:
public ObservableCollection<Models.Stockholm.ParkingLocation> ParkingLocations { get; set; }
next, a routine to wire that collection to the library and the underlying API:
var rxlocations = sthlmservicelayer.GetParkingLocationsByRadius(new SearchCriterias.Stockholm.Parking.ParkingLocation.ParkingLocationsByRadius(500, Models.Enums.Stockholm.VehicleTypeEnum.Bike)) .ObserveOnDispatcher() .Subscribe( // result x => { ParkingLocations.Add(x); }, // exception ex => { // handle exception.. });
And that’s it – the ParkingLocations collection is ready to go!
how does it work?
First, there’s the ServiceLayer which acts as the point of entry to the framework.
namespace Usoniandream.WindowsPhone.LocationServices.Service.Stockholm.Reactive { public class ServiceLayer : LocationServices.Service.Reactive.GenericServiceLayer { } }
In each ServiceLayer (there’s one for observable calls, and one using standard RestSharp async pattern) the methods are listed one after the other:
public IObservable<Models.Stockholm.ParkingLocation> GetParkingLocationsByRadius(SearchCriterias.Stockholm.Parking.ParkingLocation.ParkingLocationsByRadius criteria) { return ExecuteRequestReturnObservable<Models.Stockholm.ParkingLocation, Models.JSON.Stockholm.ParkingPlaces.RootObject>(criteria); }
These use one of (currently) four available methods found in the base class GenericServiceLayer:
public IObservable<Ttarget> ExecuteRequestReturnObservable<Ttarget, Tsource>(SearchCriterias.ISearchCriteria<Ttarget, Tsource> criteria) where Tsource : new() { if (String.IsNullOrWhiteSpace(criteria.Client.BaseUrl)) { throw new ArgumentException("missing 'baseurlresourcename', please check App.xaml.", "baseurlresourcename"); } if (String.IsNullOrWhiteSpace(criteria.APIkey)) { throw new ArgumentException("missing api key, please check App.xaml.", "APIkey"); } return criteria.Client.ExecuteAsync<Tsource>(criteria.Request) .SelectMany<Tsource, Ttarget>(x => criteria.Mapper.JSON2Model(x)); }
Using Reactive Extensions and a mapper (see here for an example of a mapper) the JSON response is transformed to a local model representation. The in param for each method is of type SearchCriteria which simply acts as a strong-typed request definition while in turn holds key data such as endpoint url,parameters and more.
namespace Usoniandream.WindowsPhone.LocationServices.Models.Stockholm { public class ParkingLocation : ILocation { public virtual GeoCoordinate Location { get; set; } public virtual object Content { get; set; } public List<List<double>> Coordinates { get; set; } public string CityDistrict { get; set; } public int ParkingTimeTotalMinutes { get { int total = (MaxDays.GetValueOrDefault(0) * 24 * 60) + (MaxHours.GetValueOrDefault(0) * 60) + MaxMinutes.GetValueOrDefault(0); if (total > 0) { return total; } else { return int.MaxValue; } } } public int? MaxDays { get; set; } public int? MaxHours { get; set; } public int? MaxMinutes { get; set; } public string OtherInfo { get; set; } public int? Meters { get; set; } public string Type { get; set; } public string StartWeekDay { get; set; } } }
The SearchCriteria mentioned above is also structured in a fairly generic manner:
namespace Usoniandream.WindowsPhone.LocationServices.SearchCriterias.Stockholm.Parking.ParkingLocation { public class ParkingLocationsByRadius : ParkingLocationsBase { public ParkingLocationsByRadius(int radius, Usoniandream.WindowsPhone.LocationServices.Models.Enums.Stockholm.VehicleTypeEnum vehicletype) : base(vehicletype) { Radius = radius; Request.Resource += "within?"; Request.AddParameter("radius", Radius); Request.AddParameter("lat", Location.Latitude.ToString().Replace(",", ".")); Request.AddParameter("&lng",Location.Longitude.ToString().Replace(",", ".")); } public GeoCoordinate Location { get; set; } public int Radius { get; private set; } } }
as you might notice, the criteria above inherits a ParkingsLocationBase, here’s what that looks like:
namespace Usoniandream.WindowsPhone.LocationServices.SearchCriterias.Stockholm.Parking.ParkingLocation { public abstract class ParkingLocationsBase : SearchCriteriaBase<Models.Stockholm.ParkingLocation, Usoniandream.WindowsPhone.LocationServices.Models.JSON.Stockholm.ParkingPlaces.RootObject> { public ParkingLocationsBase(Usoniandream.WindowsPhone.LocationServices.Models.Enums.Stockholm.VehicleTypeEnum vehicletype) : base("STHLM_DATA_SERVICE_URI_PARKINGLOCATION") { Mapper = new Usoniandream.WindowsPhone.LocationServices.Mappers.Stockholm.Parking.ParkingLocation(); APIKeyResourceName = "STHLM_DATA_API_KEY_PARKING"; VehicleType = vehicletype; switch (this.VehicleType) { case Usoniandream.WindowsPhone.LocationServices.Models.Enums.Stockholm.VehicleTypeEnum.Car: Request.Resource += "ptillaten/"; break; case Usoniandream.WindowsPhone.LocationServices.Models.Enums.Stockholm.VehicleTypeEnum.Bike: Request.Resource += "pmotorcykel/"; break; case Usoniandream.WindowsPhone.LocationServices.Models.Enums.Stockholm.VehicleTypeEnum.Truck: Request.Resource += "plastbil/"; break; case Usoniandream.WindowsPhone.LocationServices.Models.Enums.Stockholm.VehicleTypeEnum.Handicap: Request.Resource += "prorelsehindrad/"; break; default: throw new ArgumentException("VehicleType must be set", "VehicleType"); } Request.AddParameter("outputFormat", "json"); Request.AddParameter("apiKey", APIkey); } public Usoniandream.WindowsPhone.LocationServices.Models.Enums.Stockholm.VehicleTypeEnum VehicleType { get; private set; } } }
And, in turn, the ParkingLocationBase inherits from SearchCriteriaBase which adds the following to the mix:
public abstract class SearchCriteriaBase<Ttarget, Tsource> : INotifyPropertyChanged, Usoniandream.WindowsPhone.LocationServices.SearchCriterias.ISearchCriteria<Ttarget, Tsource> { public SearchCriteriaBase() { Request = new RestRequest(); Client = new RestClient(); } public SearchCriteriaBase(string baseurlresourcename) { Request = new RestRequest(); if (string.IsNullOrWhiteSpace(baseurlresourcename)) { throw new ArgumentException("missing 'baseurlresourcename', please check App.xaml.", "baseurlresourcename"); } Client = new RestClient(((Models.ServiceURI)Application.Current.Resources[baseurlresourcename]).URL); } public SearchCriteriaBase(Method method) { Request = new RestRequest(method); Client = new RestClient(); } public SearchCriteriaBase(Method method, string baseurlresourcename) { Request = new RestRequest(method); if (string.IsNullOrWhiteSpace(baseurlresourcename)) { throw new ArgumentException("missing 'baseurlresourcename', please check App.xaml.", "baseurlresourcename"); } Client = new RestClient(((Models.ServiceURI)Application.Current.Resources[baseurlresourcename]).URL); } public RestClient Client { get; private set; } public RestRequest Request { get; private set; } public IMapper<Ttarget, Tsource> Mapper { get; set; } public Dictionary<string, string> Parameters { get; set; } public virtual string APIKeyResourceName { get; protected set; } private string apikey = null; public virtual string APIkey { get { if (string.IsNullOrWhiteSpace(apikey)) { if (!String.IsNullOrWhiteSpace(APIKeyResourceName)) { return ((Models.ServiceAPIKey)Application.Current.Resources[APIKeyResourceName]).Value; } if (!string.IsNullOrWhiteSpace(apikey)) { return apikey; } else { throw new ArgumentException("missing api key, please check App.xaml.", "APIkey"); } } return apikey; } private set { apikey = value; } } public event PropertyChangedEventHandler PropertyChanged; }
So, there you have it. The engine parts that more or less make up the majority of the framework.
The framework libraries, source code and sample app are all on GitHub: https://github.com/peterdrougge/Usoniandream.WIndowsPhone.LocationServices
No comments:
Post a Comment