Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.vs/
bin/
obj/
Debug/
Release/
34 changes: 12 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
# simpleOrderSearch
simpleOrderSearch
This is my SimpleOrderSearch REST API service (and console client) attempt after ~4 hours of work. This was my first time creating a REST API service from scratch and my first ever ASP.net project, so the ~4 hours are both learning and doing.

The concepts seemed somewhat familiar/easy, like how routes work. Fair amount of fumbling about for exact class/nuget-package to do what I want.

I want to assess your ability to create an application and REST API service. It truly is the bare minimum of knowledge necessary to be successful in this position. I don't want you to spend a lot of time on this. You should be able to do this in an hour or so if the job is right for you.
Also, the specification requiring an OrderID AND a CompletionDte seemed so
wrong that I implemented things as if valid search criteria are:
1: OrderID, or...
2: MSA and Status and CompletionDte

Order Search
I would change things the moment I become convinced that always requiring the
CompletionDte is a good/necessary thing.

This programming task consists of building a simple console application to search for orders. Fork this repository and create your application. It should take this input from the user:
To run my stuff, open each solution in Visual Studio 2019 and start debugging.

(Order Number || (MSA && Status)) && CompletionDte

The console application will call a service that you create using C#. I have provided some sample data for the application in the JSON file in the data folder.



The file contains an array whose elements represent orders. The data should be defined as a model in your service.

The application calling the service can be a console app. You have total freedom to do what you want but make sure it can do these three things:

• Validate that the user has provided the right criteria to make a search

• Provide an offset and page value for pagination.

• Write the outputs of the service call to a console window.

Create a pull request once you have it working. I will clone your repository, verify that it works, and evaluate it. Please ensure you include any instructions for running that may be required.
See `example_client_session.txt` for an example of a client session with both
searching by OrderID and searching by {MSA, Status, CompletionDte} with
pagination.
163 changes: 163 additions & 0 deletions SimpleOrderSearchClient/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using RestSharp;
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Newtonsoft.Json;

namespace SimpleOrderSearchClient
{
class Program
{
static string ApiUrl = "http://localhost:6627/api/Order";
static RestClient Client = new RestClient(ApiUrl);

static void Main(string[] args)
{
MenuMain();
}

static void MenuMain()
{
string mainChoice;

do
{
Console.WriteLine("[Main Menu]");
Console.WriteLine("'o' to search by OrderID");
Console.WriteLine("'m' to search by {MSA, Status, CompletionDte}");
Console.WriteLine("'q' to quit");
Console.Write("your choice: ");

mainChoice = Console.ReadLine();
Console.WriteLine();

if (mainChoice == "o")
{
SearchByOrderId();
}
else if (mainChoice == "m")
{
SearchByMsaStatusCompletionDte();
}

Console.WriteLine();
} while (mainChoice != "q");
}

static void SearchByOrderId()
{
Console.WriteLine("[Searching By OrderID]");

long orderID;
if (!getUserLong("OrderID", out orderID))
{
return;
}

var request = new RestRequest(Method.GET);
request.AddParameter("orderID", orderID);

var response = Client.Execute(request);
if (response.IsSuccessful)
{
Console.WriteLine(" success!");
var orders = JsonConvert.DeserializeObject<List<object>>(response.Content);
Console.WriteLine(JsonConvert.SerializeObject(orders, Formatting.Indented));
}
else
{
Console.WriteLine(" error!");
Console.WriteLine($" Status Code: {response.StatusCode}");
Console.WriteLine($" Status Description: {response.StatusDescription}");
Console.WriteLine($" Error Msg: {response.ErrorMessage}");
}
}

static void SearchByMsaStatusCompletionDte()
{
Console.WriteLine("[Searching By {MSA, Status, CompleteDte}]");
long msa;
long status;
DateTime completionDte;

if (!getUserLong("MSA", out msa)
|| !getUserLong("Status", out status)
|| !getUserDateTime("CompletionDte", out completionDte))
{
return;
}

int page = 0;

while (displaySearchResults(msa, status, completionDte, page))
{
page++;
Console.Write("want next page (y/n)? ");

if (Console.ReadLine() != "y")
{
break;
}
}
}

static bool displaySearchResults(long msa, long status, DateTime completionDte, int page)
{
var request = new RestRequest(Method.GET);
request.AddParameter("msa", msa);
request.AddParameter("status", status);
request.AddParameter("completionDte", completionDte);
request.AddParameter("page", page);

var response = Client.Execute(request);
bool gotOrders = false;

if (response.IsSuccessful)
{
Console.WriteLine(" success!");
var orders = JsonConvert.DeserializeObject<List<object>>(response.Content);
Console.WriteLine(JsonConvert.SerializeObject(orders, Formatting.Indented));

gotOrders = orders.Count > 0;
}
else
{
Console.WriteLine(" error!");
Console.WriteLine($" Status Code: {response.StatusCode}");
Console.WriteLine($" Status Description: {response.StatusDescription}");
Console.WriteLine($" Error Msg: {response.ErrorMessage}");
}

return gotOrders;
}

static bool getUserLong(string valName, out long val)
{
Console.Write($"enter {valName}: ");
string valStr = Console.ReadLine();

if (!long.TryParse(valStr, out val))
{
Console.WriteLine($"invalid {valName}, returning to previous menu");
return false;
}

return true;
}

static bool getUserDateTime(string valName, out DateTime val)
{
Console.Write($"enter {valName}: ");
string valStr = Console.ReadLine();

if (!DateTime.TryParse(valStr, out val))
{
Console.WriteLine($"invalid {valName}, returning to previous menu");
return false;
}

return true;
}

}
}
13 changes: 13 additions & 0 deletions SimpleOrderSearchClient/SimpleOrderSearchClient.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="RestSharp" Version="106.6.10" />
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions SimpleOrderSearchClient/SimpleOrderSearchClient.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29609.76
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleOrderSearchClient", "SimpleOrderSearchClient.csproj", "{53C14057-1996-45B9-A465-0F0F3D573F2D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{53C14057-1996-45B9-A465-0F0F3D573F2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53C14057-1996-45B9-A465-0F0F3D573F2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53C14057-1996-45B9-A465-0F0F3D573F2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53C14057-1996-45B9-A465-0F0F3D573F2D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {57F322C1-9AD3-46B8-A62B-32D34FCA7C73}
EndGlobalSection
EndGlobal
98 changes: 98 additions & 0 deletions SimpleOrderSearchService/Controllers/OrderController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http.Results;
using Microsoft.AspNetCore.Mvc;
using SimpleOrderSearchService.Models;
using SimpleOrderSearchService.Services;
using SimpleOrderSearchService.Util;

// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace SimpleOrderSearchService.Controllers
{
[Route("api/[controller]")]
public class OrderController : Controller
{
private readonly IOrderService _orderService;
private readonly int _defaultItemsPerPage = 3;

public OrderController(IOrderService orderService)
{
_orderService = orderService;
}

// note: returning IActionResult instead of the actual data type so we can also return errors

// GET: api/<controller>
[HttpGet]
public IActionResult Get
( [FromQuery] long? orderID
, [FromQuery] long? msa
, [FromQuery] long? status
, [FromQuery] DateTime? completionDte
, [FromQuery] int? page
, [FromQuery] int? itemsPerPage
)
{
// sorry, but the specification requiring an OrderID AND a
// CompletionDte seems so wrong that I'll be implementing things as
// if valid search criteria are:
// 1: OrderID
// 2: MSA and Status and CompletionDte
// and am very open to changing things once I become convinced that
// always requiring the CompletionDte is a good/necessary thing

// yes, I am assuming OrderID will be unique

if (orderID.HasValue)
{
return Ok(_orderService.GetByOrderID(orderID.Value));
}
else if (msa.HasValue && status.HasValue && completionDte.HasValue)
{
var safePageIdx = page.HasValue ? page.Value : 0;
var safeItemsPerPage = itemsPerPage.HasValue ? itemsPerPage.Value : _defaultItemsPerPage;

var orders = _orderService.GetByMsaStatusCompletionDte(msa.Value, status.Value, completionDte.Value);
var pagedOrders = Pagination.SelectItems(orders, safeItemsPerPage, safePageIdx);
return Ok(pagedOrders);
}

return ValidationProblem("you need to specify orderID or {msa, status, completionDte}");
//return Ok(_orderService.GetAll());
}

// GET api/<controller>/5
[HttpGet("{orderID}")]
public IActionResult Get(long orderID)
{
var order = _orderService.GetByOrderID(orderID).FirstOrDefault();
if (order == null)
{
return NotFound();
}

return Ok(order);
}

// POST api/<controller>
[HttpPost]
public void Post([FromBody]string value)
{
}

// PUT api/<controller>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}

// DELETE api/<controller>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
File renamed without changes.
20 changes: 20 additions & 0 deletions SimpleOrderSearchService/Models/Order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace SimpleOrderSearchService.Models
{
public class Order
{
public long OrderID { get; set; }
public long ShipperID { get; set; }
public long DriverID { get; set; }
public DateTime CompletionDte { get; set; }
public long Status { get; set; }
public string Code { get; set; }
public long MSA { get; set; }
public decimal Duration { get; set; }
public long OfferType { get; set; }
}
}
26 changes: 26 additions & 0 deletions SimpleOrderSearchService/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace SimpleOrderSearchService
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Loading