Cox Automotive Interview Challenge
Implementation and explanation of Cox Automotive Programming Interview challenge
Introduction
This API challenge was sent to me by an old work colleague who was interviewing at different companies, he decided to do this project in JS as he was a front-end developer and I decided to try my hand at this as a backend developer using my recently acquired C# skills.
Prerequisites
- .NET Core (Framework works AFAIK)
- Newtonsoft.Json (NuGet Package freely available)
- Internet Access to API server (https://api.coxauto-interview.com/)
Background
The challenge can be found here!
So this challenge we are given a bunch of REST API Endpoints (in the form of Swagger) and our goal is to retrieve different information from each endpoint, do some mapping, and return it back to the endpoint in a specific format.
So to accomplush this a high level overview would be the following:
- Generate a new DataSetID
- Retrieve all the VehicleIDs from a DataSetID
- Retrieve all the VehicleInfos for each VehicleID
- Retrieve all the DealerInfo for each DealerID (found within VehicleInfo)
- Format the relevant information into desired format
- Send back the information and ensure we got the correct answer!
So we would hit the endpoints in the following order
- GET /api/datasetId
- Creates new dataset and return its ID
- GET /api/{datasetId}/vehicles
- Get a list of all vehicle ids in a dataset
- GET /api/{datasetId}/vehicles/{vehicleId}
- Get specific information about vehicle
- GET /api/{datasetId}/dealers/{dealerId}
- Gets specific information about dealer
- POST /api/{datasetId}/answer
- Send information back and see if it is correct
Code
To start I am going to explain how I mapped the Request and Response Models. Every single REST call generally returns a JSON formatted object. What I did was recreate all of their response models as C# objects. That way when I retrieve the response from the REST API I am able to use the build JSON Serialize to create the object and have all the properties matched. All the models can be found here
Here is an example response for the Vehicle Info Response. Note, that the “[JsonProperty(“”)]” attribute denotes the name of the field in the json payload. Most of the times you don’t need it as long property name matches the json response but I put it here just for reference.
The below spoilers show how the response from their API can get mapped to a model in C#.
Example JSON Response & Model
{
"vehicleId": 1922782095,
"year": 2014,
"make": "Ford",
"model": "F150",
"dealerId": 939619781
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CoxApi.Models
{
class VehicleResponse
{
[JsonProperty("vehicleId")]
public int VehicleId { get; set; }
[JsonProperty("year")]
public int Year { get; set; }
[JsonProperty("make")]
public string Make { get; set; }
[JsonProperty("model")]
public string model { get; set; }
[JsonProperty("dealerId")]
public int DealerId { get; set; }
}
}
The next major class is our BaseProxy. This is a generic class that is a wrapper around the HttpClient from .NET. I could've merged it into a single file but since I use HTTP calls a lot I pulled a simplified version out of another project and added it here. This code's job is just to execute GET and POST requests. For the GET request it's just requesting the data, and derserializing it into the specified object. The POST request serialized the JSON payload into a StringContent object in order for it to be attached correctly.
Base Proxy (CLICK ME)
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace CoxApi
{
class BaseProxy
{
private HttpClient _client;
public BaseProxy()
{
_client = new HttpClient();
}
public async Task GetAsync(string rootUrl)
{
var response = await _client.GetAsync(rootUrl);
return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
}
public async Task PostAsync(string rootUrl, U data)
{
var jsonPayload = JsonConvert.SerializeObject(data);
var response = await _client.PostAsync(rootUrl, PrepJsonForPost(jsonPayload));
return JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
}
private StringContent PrepJsonForPost(string jsonObj)
{
return new StringContent(jsonObj, Encoding.UTF8, "application/json");
}
}
}
The next class is the CoxService class (Technically if we followed correct patterns we should have a CoxService class that calls a CoxProxy which implements Base Proxy but since its a small programming challenge I cut corners).
This is the class that obtains all the data from the endpoints VIA the URLs defined at the top, merges all the information and then sends it back to the endpoint. The mapping using LINQ (highly recommend looking into this, very powerful) to generate the response.
A section that is critical is the GetAllObjects() function. One of the specifics of this challenge was that one of the calls has a built in delay, which means if you call it sequentially all the times will stack. IE if each call takes 5 seconds and you call it 10 times, it would take 50 seconds if you did them sequentially. To fix this we have to run the calls in Parallel, that way the thread will free up as soon as the call is made to make another call.
We do this using .NET's System.Threading.Task, we add the calls to the Task list and then await on the entire list, which means it will fire all our HTTP requests in parallel and then return when all of them have completed.
Cox Service (CLICK ME)
using CoxApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CoxApi
{
class CoxService : BaseProxy
{
private readonly string BaseUrl = "https://api.coxauto-interview.com";
private readonly string GetApiDatasetId = "/api/datasetId";
private readonly string PostAnswer = "/api/{0}/answer";
private readonly string GetDealers = "/api/{0}/dealers/{1}";
private readonly string GetVehicles = "/api/{0}/vehicles";
private readonly string GetSpecificVehicle= "/api/{0}/vehicles/{1}";
public async Task GenerateCoxAutoCatalog()
{
//Create a new dataset object
var dataSetIdResponse = await GetAsync(
BaseUrl + GetApiDatasetId);
//Obtain all of the vehicles
var vehiclesResponse = await GetAsync(
BaseUrl + string.Format(GetVehicles, dataSetIdResponse.DatasetId));
var vehicles = await GetAllObject(
GetSpecificVehicle, dataSetIdResponse.DatasetId, vehiclesResponse.VehicleIds);
//Obtain all the dealers
var dealerIds = vehicles.Select(x => x.DealerId);
var dealers = await GetAllObject(
GetDealers, dataSetIdResponse.DatasetId, dealerIds);
//Mapping all the objects above to the correct Answer format
var aDealers = dealers.GroupBy(x => x.DealerId).Select(x => x.First()).Select(dealer => new Dealer()
{
DealerId = dealer.DealerId,
Name = dealer.Name,
Vehicles = vehicles.Where(x => x.DealerId.Equals(dealer.DealerId)).Select(x => new Vehicle()
{
Make = x.Make,
Model = x.model,
VehicleId = x.VehicleId,
Year = x.Year
})
});
//Post back our answer and see if it is correct!
return await PostAsync(BaseUrl + string.Format(PostAnswer, dataSetIdResponse.DatasetId), new AnswerRequest() { Dealers = aDealers });
}
private async Task> GetAllObject(string endpoint, string datasetId, IEnumerable ids)
{
var tasks = new List>();
foreach (var id in ids)
{
tasks.Add(GetAsync(BaseUrl + string.Format(endpoint, datasetId, id)));
}
return await Task.WhenAll(tasks);
}
}
}
Conclusion
The model classes are missing from here but in the source code, and worst case you can always practice by coming up with the JSON mappings yourself for practice deserializing.
Overall this was a very fun project that gave me some hands on experience with REST API and writing code to obtain and send information in C#. One of the things that I really had to pay attention too when doing this was the Async calls, in particular take a look at the GetAllObject