All posts Programming

Cox Automotive Interview Challenge

Implementation and explanation of Cox Automotive Programming Interview challenge using C# and .NET, covering parallel REST API calls and JSON mapping.

Alexander Sigler Updated January 6, 2023 6 min read
C#CSharp.NETInterview
Cox Automotive 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

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 accomplish this a high level overview would be the following:

  1. Generate a new DataSetID
  2. Retrieve all the VehicleIDs from a DataSetID
  3. Retrieve all the VehicleInfos for each VehicleID
  4. Retrieve all the DealerInfo for each DealerID (found within VehicleInfo)
  5. Format the relevant information into desired format
  6. Send back the information and ensure we got the correct answer!

So we would hit the endpoints in the following order:

  1. GET /api/datasetId — Creates new dataset and returns its ID
  2. GET /api/{datasetId}/vehicles — Get a list of all vehicle IDs in a dataset
  3. GET /api/{datasetId}/vehicles/{vehicleId} — Get specific information about a vehicle
  4. GET /api/{datasetId}/dealers/{dealerId} — Gets specific information about a dealer
  5. 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 built-in JSON Serializer 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 time you don’t need it as long as the property name matches the JSON response but I put it here just for reference.

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 deserializing it into the specified object. The POST request serializes the JSON payload into a StringContent object in order for it to be attached correctly.

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
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<T> GetAsync<T>(string rootUrl)
        {
            var response = await _client.GetAsync(rootUrl);
            return JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
        }

        public async Task<T> PostAsync<T, U>(string rootUrl, U data)
        {
            var jsonPayload = JsonConvert.SerializeObject(data);
            var response = await _client.PostAsync(rootUrl, PrepJsonForPost(jsonPayload));
            return JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
        }

        private StringContent PrepJsonForPost(string jsonObj)
        {
            return new StringContent(jsonObj, Encoding.UTF8, "application/json");
        }
    }
}

The next class is the CoxService class. 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 uses LINQ 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 sequentially. To fix this we have to run the calls in parallel, that way the thread will fire all HTTP requests concurrently and return when all of them have completed.

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<AnswerResponse> GenerateCoxAutoCatalog()
        {
            // Create a new dataset object
            var dataSetIdResponse = await GetAsync<DataSetIdResponse>(
                BaseUrl + GetApiDatasetId);

            // Obtain all of the vehicles
            var vehiclesResponse = await GetAsync<VehiclesResponse>(
                BaseUrl + string.Format(GetVehicles, dataSetIdResponse.DatasetId));
            var vehicles = await GetAllObject<VehicleResponse>(
                GetSpecificVehicle, dataSetIdResponse.DatasetId, vehiclesResponse.VehicleIds);

            // Obtain all the dealers
            var dealerIds = vehicles.Select(x => x.DealerId);
            var dealers = await GetAllObject<DealerResponse>(
                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<AnswerResponse, AnswerRequest>(
                BaseUrl + string.Format(PostAnswer, dataSetIdResponse.DatasetId),
                new AnswerRequest() { Dealers = aDealers });
        }

        private async Task<T[]> GetAllObject<T>(string endpoint, string datasetId, IEnumerable<int> ids)
        {
            var tasks = new List<Task<T>>();
            foreach (var id in ids)
            {
                tasks.Add(GetAsync<T>(BaseUrl + string.Format(endpoint, datasetId, id)));
            }
            return await Task.WhenAll(tasks);
        }
    }
}

Conclusion

The model classes are missing from here but can be found in the source code, and worst case you can always practice by coming up with the JSON mappings yourself.

Overall this was a very fun project that gave me some hands-on experience with REST APIs and writing code to obtain and send information in C#. One of the things that I really had to pay attention to was the async calls — in particular take a look at the GetAllObject<T> function in the Cox Service. All those queries had to be run in parallel otherwise the built-in delay would’ve put it past our 30 second time limit. We utilized a Task list and awaited until they were all done running in parallel.