using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Collections.Generic; using AdvancedUtility.WebApi.Models; using Newtonsoft.Json; using System.Text; using System.IO; using Newtonsoft.Json.Linq; namespace WebApiSample { /* * Example use of Advanced REST Web API interactions using .NET 4.5 web client. * AdvancedUtility.WebApi.Models.dll is available from advanced as a stand-alone reference library for use in your projects * (access the models DLL via REST URL https://webapi.yourserver.com/Content/AdvancedUtility.WebApi.Models.dll ) * Need to install: * - Microsoft.AspNet.WebApi.Client * - Newtonsoft.Json * - Newtonsoft.Json.Linq */ class StateSample { // these values must be adjusted to your environment in order for the sample to work static string _webApiUser = "cis_userid"; // CIS UserId to be used to authenticate with WebApi static string _webApiPassword = "cis_password"; // CIS password associated with the CIS UserId above static string _webApiRootUrl = "https://webapi.yourserver.com/"; // URL Root of the REST Web API server to be used public static void Main() { try { RunAsync().Wait(); } catch (AggregateException ex) { Console.WriteLine("Errors occurred during the HTTP interactions with the REST WebApi server:"); foreach (var item in ex.InnerExceptions) { Console.WriteLine(item.ToString()); } } } private static async Task RunAsync() { using (var client = new HttpClient()) { client.BaseAddress = new Uri(_webApiRootUrl); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var basicAuth = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", _webApiUser, _webApiPassword)))); client.DefaultRequestHeaders.Authorization = basicAuth; // === HTTPS GET List // 1. format query string for request // eg. page=2&pagesize=5&where=code like '%N%' and o_key gt 5&order=-description&fields=* var qsitems = new Dictionary(); qsitems.Add("page", "2"); // optional page to select - defaults to 1 qsitems.Add("pagesize", "5"); // optional pagesize of result - defaults to 50 qsitems.Add("where", "code like '%N%' and O_Key gt 5"); // optional selection filter - defaults to none qsitems.Add("order", "-description"); // optional sort order of result - defaults to pk order qsitems.Add("fields", "*"); // optional data shaping directive - defaults to "*" var qslist = new List(); foreach (var item in qsitems) { qslist.Add(String.Format("{0}={1}", item.Key, item.Value)); } var qs = "?" + string.Join("&", qslist); // 2. create full URL for the request var listUri = new Uri(client.BaseAddress, "data/state" + qs); HttpResponseMessage response = await client.GetAsync(listUri); if (response.IsSuccessStatusCode) { // HTTP response code 200 (OK) indicates the request succeeded, and the response body contains a result list of type "ListResourceModel" ListResourceModel list = await response.Content.ReadAsAsync(); Console.WriteLine("Successfully retrieved list of states: Page={0}, PageSize={1}, TotalPages={2}, TotalItems={3}", list.CurrentPage, list.PageSize, list.TotalPages, list.TotalItems); // now find the list of states in the _embedded dictionary if (list._embedded != null && list._embedded.ContainsKey("state") && list._embedded["state"] != null) { List ls = Newtonsoft.Json.JsonConvert.DeserializeObject>(list._embedded["state"].ToString()); if (ls != null) { foreach (var state in ls) { Console.WriteLine("\t{0}\t{1}\t{2}", state.Code, state.Description, state.O_Key); } } } } else { Console.WriteLine("GET(list) failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } // === HTTPS GET One // Retrieve a single resource response = await client.GetAsync("data/state/NY"); // Can optionally add query string "fields=" for data shaping - defaults to "*" // eg. response = await client.GetAsync("data/state/NY?fields=*"); if (response.IsSuccessStatusCode) { // Response code 200 (OK) indicates the GET was successful and the response body contains the resource object requested. StateModel state = await response.Content.ReadAsAsync(); Console.WriteLine("Successfully retrieved one resource from State Control: {0}\t{1}\t{2}", state.Code, state.Description, state.O_Key); } else { Console.WriteLine("GET(one) failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } // === HTTPS VALIDATE // Validate a single existing resource against business rules var request = new HttpRequestMessage(HttpMethod.Get, "data/state/NY"); // Use X_HTTP-Method-Override header to issue VALIDATE method because HttpClient does not support custom REST method VALIDATE. // CIS Web API server allows this header to be passed on a POST or GET in order to mimic a VALIDATE or other unsupported methods. request.Headers.Add("X-HTTP-Method-Override", "VALIDATE"); response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { // Response code 200 (OK) indicates the VALIDATE was successful and there are no validation issues with the resource. Console.WriteLine("Successfully validated resource /data/state/NY"); } else { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest && response.ReasonPhrase == "Validation failed") { // Response code 400 with adjusted status message "Validation failed" - means the response contains List with details of the failures var content = response.Content.ReadAsStringAsync().Result.ToString(); JArray jsonContent = JArray.Parse(content); Console.WriteLine("Request failed with code: " + response.StatusCode + " Validation failed"); // list the details of each validation exception message foreach (JObject obj in jsonContent.Children()) { foreach (JProperty prop in obj.Properties()) { string name = prop.Name; string value = (string)prop.Value; if (value == null) value = "null"; if (name != "" && name != null) Console.WriteLine(name + " : " + value); } Console.WriteLine(); } } else { Console.WriteLine("VALIDATE failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } } // === HTTPS VALIDATE for add // Validate a given new resource model for add { var requestAdd = new HttpRequestMessage(HttpMethod.Post, "data/state"); // Use X_HTTP-Method-Override header to issue VALIDATE method because HttpClient does not support custom REST method VALIDATE. // CIS Web API server allows this header to be passed on a POST or GET in order to mimic a VALIDATE or other unsupported methods. requestAdd.Headers.Add("X-HTTP-Method-Override", "VALIDATE"); // prepare JSON prospective state model to validate for add var valState = new StateModel() { Code = "ZZX", Description = "" }; // invalid - code too long and description is required var json = JsonConvert.SerializeObject(valState, Formatting.Indented); requestAdd.Content = new StringContent(json, Encoding.UTF8, "application/json"); // send POST with new resource model as data using override header so server interprets as VALIDATE call response = await client.SendAsync(requestAdd); if (response.IsSuccessStatusCode) { // Response code 200 (OK) indicates the VALIDATE was successful and there are no validation issues with the resource. Console.WriteLine("Successfully validated new resource for add"); } else { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest && response.ReasonPhrase == "Validation failed") { // Response code 400 with adjusted status message "Validation failed" - means the response contains List with details of the failures var content = response.Content.ReadAsStringAsync().Result.ToString(); JArray jsonContent = JArray.Parse(content); Console.WriteLine("Request failed with code: " + response.StatusCode + " Validation failed"); // list the details of each validation exception message foreach (JObject obj in jsonContent.Children()) { foreach (JProperty prop in obj.Properties()) { string name = prop.Name; string value = (string)prop.Value; if (value == null) value = "null"; if(name != "" && name != null) Console.WriteLine(name + " : " + value); } Console.WriteLine(); } } else { Console.WriteLine("VALIDATE failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } } } // === HTTPS POST // Add a new resource var newState = new StateModel() { Code = "FA", Description = "State of Mind" }; response = await client.PostAsJsonAsync("data/state", newState); if (response.IsSuccessStatusCode) { // POST response code 201 (Created) indicates success, and the new resource is returned in the response content body // the response header "Location" returns the URI of the newly created resource Uri stateUrl = response.Headers.Location; Console.WriteLine("Successfully created new state at location: {0}", stateUrl); // not standard REST, but the Advanced WebApi returns the created data as content on the POST response, so you can see any defaulted values (eg. the assigned PK) newState = await response.Content.ReadAsAsync(); Console.WriteLine("New state was assigned a PK of {0}", newState.O_Key); // === HTTPS VALIDATE for update // Validate a given resource update model for changes { var requestUpdate = new HttpRequestMessage(HttpMethod.Post, stateUrl); // Use X_HTTP-Method-Override header to issue VALIDATE method because HttpClient does not support custom REST method VALIDATE. // CIS Web API server allows this header to be passed on a POST or GET in order to mimic a VALIDATE or other unsupported methods. requestUpdate.Headers.Add("X-HTTP-Method-Override", "VALIDATE"); // prepare JSON state update model to validate for changes newState.Description = "This description is too long to be valid"; // Invalid - too long var json = JsonConvert.SerializeObject(newState, Formatting.Indented); requestUpdate.Content = new StringContent(json, Encoding.UTF8, "application/json"); // send POST with changed resource model as data using override header so server interprets as VALIDATE call response = await client.SendAsync(requestUpdate); if (response.IsSuccessStatusCode) { // Response code 200 (OK) indicates the VALIDATE was successful and there are no validation issues with the resource changes. Console.WriteLine("Successfully validated updated resource for changes"); } else { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest && response.ReasonPhrase == "Validation failed") { // Response code 400 with adjusted status message "Validation failed" - means the response contains List with details of the failures var content = response.Content.ReadAsStringAsync().Result.ToString(); JArray jsonContent = JArray.Parse(content); Console.WriteLine("Request failed with code: " + response.StatusCode + " Validation failed"); // list the details of each validation exception message foreach (JObject obj in jsonContent.Children()) { foreach (JProperty prop in obj.Properties()) { string name = prop.Name; string value = (string)prop.Value; if (value == null) value = "null"; if (name != "" && name != null) Console.WriteLine(name + " : " + value); } Console.WriteLine(); } } else { Console.WriteLine("VALIDATE for changes failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } } } // === HTTPS PUT // Edit the existing resource using PUT // (PUT typically requires a GET followed by a PUT, because PUT requires the entire object to be sent, since missing properties are defaulted to "empty" values- not what you normally want) newState.Description = "State of Being"; // Update state description response = await client.PutAsJsonAsync(stateUrl, newState); if (response.IsSuccessStatusCode) { // HTTP response code 200 (OK) indicates success. The updated resource is returned in the response content body Console.WriteLine("Successfully updated state description via PUT"); } else { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest && response.ReasonPhrase == "Validation failed") { // Response code 400 with adjusted status message "Validation failed" - means the response contains List with details of the failures var content = response.Content.ReadAsStringAsync().Result.ToString(); JArray jsonContent = JArray.Parse(content); Console.WriteLine("Request failed with code: " + response.StatusCode + " Validation failed"); foreach (JObject obj in jsonContent.Children()) { foreach (JProperty prop in obj.Properties()) { string name = prop.Name; string value = (string)prop.Value; if (value == null) value = "null"; if (name != "" && name != null) Console.WriteLine(name + " : " + value); } Console.WriteLine(); } } else { Console.WriteLine("PUT failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } } // === HTTPS PATCH // Edit the existing resource using PATCH, which allows selective operations on fields. // Refer to https://en.wikipedia.org/wiki/JSON_Patch for details on the patch document format and supported directives. var method = new HttpMethod("PATCH"); var requestPatch = new HttpRequestMessage(method, stateUrl); requestPatch.Content = new StringContent("[ { \"op\": \"replace\", \"path\": \"/description\", \"value\": \"State of Being\" } ]"); requestPatch.Content.Headers.Clear(); requestPatch.Content.Headers.Add("Content-Type", "application/json"); response = await client.SendAsync(requestPatch); if (response.IsSuccessStatusCode) { // HTTP response code 200 (OK) returned on successful PATCH, and the response content body contains the updated resource Console.WriteLine("Successfully updated state description via PATCH"); } else { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest && response.ReasonPhrase == "Validation failed") { // Response code 400 with adjusted status message "Validation failed" - means the response contains List with details of the failures var content = response.Content.ReadAsStringAsync().Result.ToString(); JArray jsonContent = JArray.Parse(content); Console.WriteLine("Request failed with code: " + response.StatusCode + " Validation failed"); foreach (JObject obj in jsonContent.Children()) { foreach (JProperty prop in obj.Properties()) { string name = prop.Name; string value = (string)prop.Value; if (value == null) value = "null"; if (name != "" && name != null) Console.WriteLine(name + " : " + value); } Console.WriteLine(); } } else { Console.WriteLine("PATCH failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } } // === HTTPS DELETE response = await client.DeleteAsync(stateUrl); if (response.IsSuccessStatusCode) { // HTTP response code 200 (OK) or 204 (No Content) returned on success, depending on the request header value X-AdvancedUtility-NoContent. Console.WriteLine("Successfully deleted state FA"); } else { Console.WriteLine("DELETE failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } } else { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest && response.ReasonPhrase == "Validation failed") { // Response code 400 with adjusted status message "Validation failed" - means the response contains List with details of the failures var content = response.Content.ReadAsStringAsync().Result.ToString(); JArray jsonContent = JArray.Parse(content); Console.WriteLine("Request failed with code: " + response.StatusCode + " Validation failed"); // list the details of each validation exception message foreach (JObject obj in jsonContent.Children()) { foreach (JProperty prop in obj.Properties()) { string name = prop.Name; string value = (string)prop.Value; if (value == null) value = "null"; if (name != "" && name != null) Console.WriteLine(name + " : " + value); } Console.WriteLine(); } } else { Console.WriteLine("POST failed with response: {0}\t{1}\n{2}", response.StatusCode, response.ReasonPhrase, response.Content); } } } } } }