From 55bb4694487b353f9089dd88f82a8f669f59e9ba Mon Sep 17 00:00:00 2001 From: Steven Nowak Date: Thu, 12 Dec 2019 13:54:49 -0600 Subject: [PATCH 1/8] pushing files to remote for inspection --- ServiceCall/Program.cs | 112 ++++++++++++++++++ ServiceCall/ServiceCall.csproj | 11 ++ .../Controllers/OrderController.cs | 79 ++++++++++++ simpleOrderSearch/Order.cs | 37 ++++++ simpleOrderSearch/OrderResult.cs | 32 +++++ simpleOrderSearch/OrderStore.cs | 51 ++++++++ 6 files changed, 322 insertions(+) create mode 100644 ServiceCall/Program.cs create mode 100644 ServiceCall/ServiceCall.csproj create mode 100644 simpleOrderSearch/Controllers/OrderController.cs create mode 100644 simpleOrderSearch/Order.cs create mode 100644 simpleOrderSearch/OrderResult.cs create mode 100644 simpleOrderSearch/OrderStore.cs diff --git a/ServiceCall/Program.cs b/ServiceCall/Program.cs new file mode 100644 index 0000000..559937d --- /dev/null +++ b/ServiceCall/Program.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace ServiceCall +{ + class Program + { + async static Task Main(string[] args) + { + int option = 0; + do + { + Console.WriteLine(); + Console.WriteLine("Choose an option"); + Console.WriteLine("1) search by order id"); + Console.WriteLine("2) search by msa and status"); + } + while ((!int.TryParse(Console.ReadLine(), out option)) || (option < 1 || option > 2)); + Dictionary dict = new Dictionary(); + Console.WriteLine(); + if (option == 1) + { + dict.Add("OrderID", validateData(() => + { + Console.Write("Enter the order number: "); + return Console.ReadLine(); + }, "[0-9]").asNumber()); + //dict.Add("OrderID", Console.ReadLine().asNumber()); + } + else + { + dict.Add("MSA", validateData(() => + { + Console.Write("Enter the MSA (value is a number): "); + return Console.ReadLine(); + }, "[0-9]").asNumber()); + Console.WriteLine(); + dict.Add("Status", validateData(() => + { + Console.Write("Enter the status (value is a number): "); + return Console.ReadLine(); + }, "[0-9]").asNumber()); + } + Console.WriteLine(); + string date = validateData(() => { + Console.WriteLine("For the completion date, first give the year (in \"yyyy-mm-dd\" format): "); + return Console.ReadLine(); + }, @"\d{4}\-\d{2}\-\d{2}"); + string time = validateData(() => { + Console.WriteLine("then the time (in \"hh:mm:ss\" format): "); + return Console.ReadLine(); + }, @"[0-6]{2}\:[0-6]{2}\:[0-6]{2}"); + //dict.Add("CompletionDte", "2018-01-12T05:10:00"); + dict.Add("CompletionDte", $"{date}T{time}"); + Console.WriteLine(); + string pagenumber = validateData(() => + { + Console.Write("What is the page number to start at (leave blank to start at the beginning)? "); + return Console.ReadLine(); + }, "[0-9]", true); + if(!String.IsNullOrEmpty(pagenumber)) + { + dict.Add("page", pagenumber.asNumber()); + } + Console.WriteLine(); + string pagesize = validateData(() => + { + Console.Write("What is the page size to start at (leave blank for all results or a default of 25 will be used if a page number was specified)? "); + return Console.ReadLine(); + }, "[0-9]", true); + if (!String.IsNullOrEmpty(pagesize)) + { + dict.Add("size", pagesize.asNumber()); + } + Console.WriteLine(); + using (HttpClient client = new HttpClient()) + { + string json = JsonConvert.SerializeObject(dict); + using (HttpContent content = new StringContent(json)) + { + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + var result = await client.PostAsync("https://localhost:5001/api/order", content); + Console.WriteLine(await result.Content.ReadAsStringAsync()); + } + + } + System.Threading.Thread.Sleep(10000); + } + + private static string validateData(Func func, string regex, bool allowBlanks = false) + { + string validate; + do + { + validate = func(); + } + while ((allowBlanks && !String.IsNullOrEmpty(validate)) && (!System.Text.RegularExpressions.Regex.IsMatch(validate, regex))); + return validate; + } + } + + public static class StringExpansions + { + public static int asNumber(this string s) + { + return int.Parse(s); + } + } +} diff --git a/ServiceCall/ServiceCall.csproj b/ServiceCall/ServiceCall.csproj new file mode 100644 index 0000000..33d276f --- /dev/null +++ b/ServiceCall/ServiceCall.csproj @@ -0,0 +1,11 @@ + + + + Exe + netcoreapp3.0 + + + + + + diff --git a/simpleOrderSearch/Controllers/OrderController.cs b/simpleOrderSearch/Controllers/OrderController.cs new file mode 100644 index 0000000..3736a59 --- /dev/null +++ b/simpleOrderSearch/Controllers/OrderController.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace simpleOrderSearch.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class OrderController : ControllerBase + { + // POST api/values + [HttpPost] + public OrderResult Post([FromBody]IDictionary request) + { + if(!request.ContainsKey("CompletionDte")) + { + throw new Exception("missing date"); + } + //now for finding an item, we can look at either order number or a combination of MSA and Status + //since OrderID is our "primary key", we look for that first before looking for MSA and Status combination + if(request.ContainsKey("OrderID")) + { + var id = request["OrderID"].GetInt64(); + //DateTime date = DateTime.Parse(request["CompletionDte"].ToString()); + DateTime date = request["CompletionDte"].GetDateTime(); + var page = (request.ContainsKey("page")) ? Math.Max(request["page"].GetInt32(), 0) : 0; + var take = (request.ContainsKey("size")) ? Math.Max(request["size"].GetInt32(), 0) : 0; + return new OrderResult() + { + results = OrderStore.instance.fetch(id, date, page, take), + pageNumber = page, + pageSize = (take == 0 && page > 0) ? 25 : take + }; + } + else if(request.ContainsKey("MSA") && request.ContainsKey("Status")) + { + long status = request["Status"].GetInt64(); + long msa = request["MSA"].GetInt64(); + DateTime date = request["CompletionDte"].GetDateTime(); + var page = (request.ContainsKey("page")) ? Math.Max(request["page"].GetInt32(), 0) : 0; + var take = (request.ContainsKey("size")) ? Math.Max(request["size"].GetInt32(), 0) : 0; + return new OrderResult() + { + results = OrderStore.instance.fetch(msa, status, date, page, take), + pageNumber = page, + pageSize = (take == 0 && page > 0) ? 25 : take + }; + } + else + { + throw new Exception("To search, you must provide either an order id or a combination of MSA and status."); + } + } + + /* + // POST api/values + [HttpPost] + public void Post([FromBody]string value) + { + } + */ + + /* + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + */ + } +} diff --git a/simpleOrderSearch/Order.cs b/simpleOrderSearch/Order.cs new file mode 100644 index 0000000..efda778 --- /dev/null +++ b/simpleOrderSearch/Order.cs @@ -0,0 +1,37 @@ +using System; +using System.Globalization; +using Newtonsoft.Json; + +namespace simpleOrderSearch +{ + public class Order + { + [JsonProperty("OrderID")] + public long OrderId { get; set; } + + [JsonProperty("ShipperID")] + public long ShipperId { get; set; } + + [JsonProperty("DriverID")] + public long DriverId { get; set; } + + [JsonProperty("CompletionDte")] + public DateTime CompletionDte { get; set; } + + [JsonProperty("Status")] + public long Status { get; set; } + + [JsonProperty("Code")] + public string Code { get; set; } + + [JsonProperty("MSA")] + public long Msa { get; set; } + + [JsonProperty("Duration")] + public string Duration { get; set; } + + [JsonProperty("OfferType")] + public long OfferType { get; set; } + } + +} diff --git a/simpleOrderSearch/OrderResult.cs b/simpleOrderSearch/OrderResult.cs new file mode 100644 index 0000000..ecccbb3 --- /dev/null +++ b/simpleOrderSearch/OrderResult.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; +using System.Collections.Generic; + +namespace simpleOrderSearch +{ + public class OrderResult + { + public IEnumerable results + { + get; + set; + } + + public int count + { + get { return results.Count(); } + } + + public int pageNumber + { + get; + set; + } + + public int pageSize + { + get; + set; + } + } +} diff --git a/simpleOrderSearch/OrderStore.cs b/simpleOrderSearch/OrderStore.cs new file mode 100644 index 0000000..3e8841f --- /dev/null +++ b/simpleOrderSearch/OrderStore.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace simpleOrderSearch +{ + public class OrderStore + { + public static OrderStore instance = new OrderStore(); + private Order[] store; + + private OrderStore() + { + string json = System.IO.File.ReadAllText(@"../data/orderInfo.json"); + store = JsonConvert.DeserializeObject(json); + } + + internal IEnumerable fetch(long id, DateTime date, int page, int size) + { + //size and page of 0 means that the user wants all instances that meet the criteria + if (page == 0 && size == 0) + { + return store.Where(q => q.OrderId == id && q.CompletionDte == date); + } + else + { + //since the user has opted for pagination, we change the number of items taken to the default page size of 25 if page size is not initially given + size = (size == 0) ? 25 : size; + var skipped = page * size; + return store.Where(q => q.OrderId == id && q.CompletionDte == date).Skip(skipped).Take(size); + } + } + + internal IEnumerable fetch(long msa, long status, DateTime date, int page, int size) + { + //size of 0 means that the user wants all instances that meet the criteria + if (page == 0 && size == 0) + { + return store.Where(q => q.Msa == msa && q.Status == status && q.CompletionDte == date); + } + else + { + //since the user has opted for pagination, we change the number of items taken to the default page size of 25 if page size is not initially given + size = (size == 0) ? 25 : size; + var skipped = page * size; + return store.Where(q => q.Msa == msa && q.Status == status && q.CompletionDte == date).Skip(skipped).Take(size); + } + } + } +} From 927f15b888c5b73074e6cbec035d9be75b89e00b Mon Sep 17 00:00:00 2001 From: Steven Nowak Date: Thu, 12 Dec 2019 14:05:49 -0600 Subject: [PATCH 2/8] cleaning up template code --- .../Controllers/OrderController.cs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/simpleOrderSearch/Controllers/OrderController.cs b/simpleOrderSearch/Controllers/OrderController.cs index 3736a59..58e8863 100644 --- a/simpleOrderSearch/Controllers/OrderController.cs +++ b/simpleOrderSearch/Controllers/OrderController.cs @@ -53,27 +53,5 @@ public OrderResult Post([FromBody]IDictionary Date: Thu, 12 Dec 2019 14:22:23 -0600 Subject: [PATCH 3/8] further refinements to property names --- ServiceCall/Program.cs | 12 ++++++------ simpleOrderSearch/Controllers/OrderController.cs | 12 ++++++------ simpleOrderSearch/OrderResult.cs | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ServiceCall/Program.cs b/ServiceCall/Program.cs index 559937d..76a3002 100644 --- a/ServiceCall/Program.cs +++ b/ServiceCall/Program.cs @@ -27,7 +27,7 @@ async static Task Main(string[] args) { Console.Write("Enter the order number: "); return Console.ReadLine(); - }, "[0-9]").asNumber()); + }, "[0-9]").AsNumber()); //dict.Add("OrderID", Console.ReadLine().asNumber()); } else @@ -36,13 +36,13 @@ async static Task Main(string[] args) { Console.Write("Enter the MSA (value is a number): "); return Console.ReadLine(); - }, "[0-9]").asNumber()); + }, "[0-9]").AsNumber()); Console.WriteLine(); dict.Add("Status", validateData(() => { Console.Write("Enter the status (value is a number): "); return Console.ReadLine(); - }, "[0-9]").asNumber()); + }, "[0-9]").AsNumber()); } Console.WriteLine(); string date = validateData(() => { @@ -63,7 +63,7 @@ async static Task Main(string[] args) }, "[0-9]", true); if(!String.IsNullOrEmpty(pagenumber)) { - dict.Add("page", pagenumber.asNumber()); + dict.Add("page", pagenumber.AsNumber()); } Console.WriteLine(); string pagesize = validateData(() => @@ -73,7 +73,7 @@ async static Task Main(string[] args) }, "[0-9]", true); if (!String.IsNullOrEmpty(pagesize)) { - dict.Add("size", pagesize.asNumber()); + dict.Add("size", pagesize.AsNumber()); } Console.WriteLine(); using (HttpClient client = new HttpClient()) @@ -104,7 +104,7 @@ private static string validateData(Func func, string regex, bool allowBl public static class StringExpansions { - public static int asNumber(this string s) + public static int AsNumber(this string s) { return int.Parse(s); } diff --git a/simpleOrderSearch/Controllers/OrderController.cs b/simpleOrderSearch/Controllers/OrderController.cs index 58e8863..f7ac30e 100644 --- a/simpleOrderSearch/Controllers/OrderController.cs +++ b/simpleOrderSearch/Controllers/OrderController.cs @@ -29,9 +29,9 @@ public OrderResult Post([FromBody]IDictionary 0) ? 25 : take + Results = OrderStore.instance.fetch(id, date, page, take), + PageNumber = page, + PageSize = (take == 0 && page > 0) ? 25 : take }; } else if(request.ContainsKey("MSA") && request.ContainsKey("Status")) @@ -43,9 +43,9 @@ public OrderResult Post([FromBody]IDictionary 0) ? 25 : take + Results = OrderStore.instance.fetch(msa, status, date, page, take), + PageNumber = page, + PageSize = (take == 0 && page > 0) ? 25 : take }; } else diff --git a/simpleOrderSearch/OrderResult.cs b/simpleOrderSearch/OrderResult.cs index ecccbb3..dc43113 100644 --- a/simpleOrderSearch/OrderResult.cs +++ b/simpleOrderSearch/OrderResult.cs @@ -6,7 +6,7 @@ namespace simpleOrderSearch { public class OrderResult { - public IEnumerable results + public IEnumerable Results { get; set; @@ -14,16 +14,16 @@ public IEnumerable results public int count { - get { return results.Count(); } + get { return Results.Count(); } } - public int pageNumber + public int PageNumber { get; set; } - public int pageSize + public int PageSize { get; set; From 269dc5d31595ac40a26105877eda8476953f9a8c Mon Sep 17 00:00:00 2001 From: Steven Nowak Date: Thu, 12 Dec 2019 14:24:34 -0600 Subject: [PATCH 4/8] (continued from previous commit) --- simpleOrderSearch/OrderResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simpleOrderSearch/OrderResult.cs b/simpleOrderSearch/OrderResult.cs index dc43113..9b330c6 100644 --- a/simpleOrderSearch/OrderResult.cs +++ b/simpleOrderSearch/OrderResult.cs @@ -12,7 +12,7 @@ public IEnumerable Results set; } - public int count + public int Count { get { return Results.Count(); } } From 6cb8f6c4859d6be0a8f090f13a74a516448c07cf Mon Sep 17 00:00:00 2001 From: Steven Nowak Date: Thu, 12 Dec 2019 16:07:40 -0600 Subject: [PATCH 5/8] realized I messed up validation for time, so I went back and added extra validation for date and time --- ServiceCall/Program.cs | 58 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/ServiceCall/Program.cs b/ServiceCall/Program.cs index 76a3002..f091f2f 100644 --- a/ServiceCall/Program.cs +++ b/ServiceCall/Program.cs @@ -45,14 +45,14 @@ async static Task Main(string[] args) }, "[0-9]").AsNumber()); } Console.WriteLine(); - string date = validateData(() => { + string date = validateDate(() => validateData(() => { Console.WriteLine("For the completion date, first give the year (in \"yyyy-mm-dd\" format): "); return Console.ReadLine(); - }, @"\d{4}\-\d{2}\-\d{2}"); - string time = validateData(() => { + }, @"\d{4}\-\d{2}\-\d{2}")); + string time = validateTime(() => validateData(() => { Console.WriteLine("then the time (in \"hh:mm:ss\" format): "); return Console.ReadLine(); - }, @"[0-6]{2}\:[0-6]{2}\:[0-6]{2}"); + }, @"[0-9]{2}\:[0-9]{2}\:[0-9]{2}")); //dict.Add("CompletionDte", "2018-01-12T05:10:00"); dict.Add("CompletionDte", $"{date}T{time}"); Console.WriteLine(); @@ -100,6 +100,56 @@ private static string validateData(Func func, string regex, bool allowBl while ((allowBlanks && !String.IsNullOrEmpty(validate)) && (!System.Text.RegularExpressions.Regex.IsMatch(validate, regex))); return validate; } + + private static string validateDate(Func func) + { + string validate; + //now we check hour, minute, and second values to see if they are kosher + //here, we will only care about 24 hour time + bool verified = false; + do + { + validate = func(); + string[] timesplits = validate.Split('-'); + //we don't care about year because those keep going on and on, but month and day are important + int month = int.Parse(timesplits[1]); + int day = int.Parse(timesplits[2]); + bool leap = (int.Parse(timesplits[0]) % 4) == 0; + //leap years are tricky + if(month == 2 && day > 0 && ((leap && day < 30) || day < 29)) + { + verified = true; + } + else if((month == 4 || month == 6 || month == 9 || month == 11) && day > 0 && day < 31) + { + verified = true; + } + else if((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && day > 0 && day < 32) + { + verified = true; + } + } + while (!verified); + return validate; + } + + private static string validateTime(Func func) + { + string validate; + //now we check hour, minute, and second values to see if they are kosher + //here, we will only care about 24 hour time + bool verified = false; + do + { + validate = func(); + string[] timesplits = validate.Split(':'); + if(int.Parse(timesplits[0]) < 24 && int.Parse(timesplits[1]) < 60 && int.Parse(timesplits[2]) < 60) { + verified = true; + } + } + while (!verified); + return validate; + } } public static class StringExpansions From 49a9deb8be89e60f2aea0eac66dd0040e8aab83a Mon Sep 17 00:00:00 2001 From: Steven Nowak Date: Thu, 12 Dec 2019 16:12:58 -0600 Subject: [PATCH 6/8] refined instructions in client program --- ServiceCall/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ServiceCall/Program.cs b/ServiceCall/Program.cs index f091f2f..4745032 100644 --- a/ServiceCall/Program.cs +++ b/ServiceCall/Program.cs @@ -58,7 +58,7 @@ async static Task Main(string[] args) Console.WriteLine(); string pagenumber = validateData(() => { - Console.Write("What is the page number to start at (leave blank to start at the beginning)? "); + Console.Write("What is the page number to start at (leave blank to start at the beginning, page numbers start at 0)? "); return Console.ReadLine(); }, "[0-9]", true); if(!String.IsNullOrEmpty(pagenumber)) @@ -68,7 +68,7 @@ async static Task Main(string[] args) Console.WriteLine(); string pagesize = validateData(() => { - Console.Write("What is the page size to start at (leave blank for all results or a default of 25 will be used if a page number was specified)? "); + Console.Write("What is the page size to start at (leave blank for all results or a default size of 25 will be used if a page number was specified and page size was left blank)? "); return Console.ReadLine(); }, "[0-9]", true); if (!String.IsNullOrEmpty(pagesize)) From e144e59ead90d7a5234788a39a0125f7c9259418 Mon Sep 17 00:00:00 2001 From: Steven Nowak Date: Thu, 12 Dec 2019 16:34:25 -0600 Subject: [PATCH 7/8] removed commented-out code --- simpleOrderSearch/Controllers/OrderController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/simpleOrderSearch/Controllers/OrderController.cs b/simpleOrderSearch/Controllers/OrderController.cs index f7ac30e..3f94bc8 100644 --- a/simpleOrderSearch/Controllers/OrderController.cs +++ b/simpleOrderSearch/Controllers/OrderController.cs @@ -23,7 +23,6 @@ public OrderResult Post([FromBody]IDictionary Date: Tue, 17 Dec 2019 11:06:46 -0600 Subject: [PATCH 8/8] * Program.cs: fixed logic in validating blank strings * ServiceCall.csproj: added readme to project * README.md: added instructions for running the service and client program --- README.md | 5 +++++ ServiceCall/Program.cs | 5 +++-- ServiceCall/ServiceCall.csproj | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b68f928..b6be834 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,8 @@ The application calling the service can be a console app. You have total freedom • 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. + + +Instructions: If you have visual studio, there is a debug/run configuration that will start the service and launch the client program. It is +called "Client and Service". Once you have it set, you can go to "Run" -> "Start Without Debugging", and the web service will start and the +client program will launch. \ No newline at end of file diff --git a/ServiceCall/Program.cs b/ServiceCall/Program.cs index 4745032..8062ad8 100644 --- a/ServiceCall/Program.cs +++ b/ServiceCall/Program.cs @@ -3,6 +3,7 @@ using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; +using System.Text.RegularExpressions; namespace ServiceCall { @@ -48,7 +49,7 @@ async static Task Main(string[] args) string date = validateDate(() => validateData(() => { Console.WriteLine("For the completion date, first give the year (in \"yyyy-mm-dd\" format): "); return Console.ReadLine(); - }, @"\d{4}\-\d{2}\-\d{2}")); + }, @"[0-9]{4}\-[0-9]{2}\-[0-9]{2}")); string time = validateTime(() => validateData(() => { Console.WriteLine("then the time (in \"hh:mm:ss\" format): "); return Console.ReadLine(); @@ -97,7 +98,7 @@ private static string validateData(Func func, string regex, bool allowBl { validate = func(); } - while ((allowBlanks && !String.IsNullOrEmpty(validate)) && (!System.Text.RegularExpressions.Regex.IsMatch(validate, regex))); + while (allowBlanks ? ((!String.IsNullOrEmpty(validate)) && (!Regex.IsMatch(validate, regex))) : (!Regex.IsMatch(validate, regex))); return validate; } diff --git a/ServiceCall/ServiceCall.csproj b/ServiceCall/ServiceCall.csproj index 33d276f..b620416 100644 --- a/ServiceCall/ServiceCall.csproj +++ b/ServiceCall/ServiceCall.csproj @@ -8,4 +8,9 @@ + + + README.md + +