API 422 response truncates data on the client side - c#

I would like to return a 422 Response from my API which includes as much of the data model that I can. I've created a method in our BaseRepository where we pass in the data, and an optional message, to return as a 422.
protected IHttpActionResult Create422Response<TData>(
TData data,
string message = "Resource validation failed."
)
{
var response = new ResponseObject<TData>(data)
{
Status = "Failure",
Message = message
};
return Content((HttpStatusCode)422, response);
}
On the client side, I'm catching the response like so:
var response = await _orderApiClient.ShowOrderForApproval(id);
if (response.StatusCode == (HttpStatusCode)422)
{
Logger.LogWarning(response.Content);
var responseJson = JObject.Parse(response.Content);
ProcessValidationErrorsFromResponse(response);
}
When I look at the value of response.Content, I see that the JSON is truncated.
If I pass the same data object through an OK response it works.
protected IHttpActionResult CreateOkResponse<TData>(TData data, string message = "Call was successful")
{
var response = new ResponseObject<TData>(data)
{
Status = "Success",
Message = message
};
return Ok(response);
}
Why would the 422 truncate the data? Could there be something else going on?
UPDATE:
Here's what ShowOrderForApproval does:
public async Task<IRestResponse> ShowOrderForApproval(int id)
{
var request = new RestRequest("/api/orders/{id}/approve/")
{
Method = Method.GET,
RequestFormat = DataFormat.Json
};
request.AddUrlSegment("id", id.ToString());
return await RsClient.SendRequestAsync(request, new HttpContextWrapper(HttpContext.Current));
}
RsClient.SendRequestAsync is:
public static Task<IRestResponse> SendRequestAsync(RestRequest request, HttpContextBase context)
{
if (_client == null)
{
_client = Connect();
}
//If null, this is coming from the CI API and we've stored the value in the originating request header
if (context.Session == null)
{
var authHeaderValue = context.Request.Headers["Authorization"];
request.AddHeader("Authorization", "Bearer " + authHeaderValue);
}
else
{
var sessionTokenName = ConfigurationManager.AppSettings["SessionTokenName"];
request.AddHeader("Authorization", "Bearer " + context.Session[sessionTokenName]);
}
return Task.FromResult(_client.Execute(request));
}
Update 2:
Okay, more updates. Using Postman I can get all the data with a 422. Our RsClient is using RestSharp.

Related

Why RestAPI occasionally returns 429 TooManyRequest response?

I send a request to the API and sometimes receive the response with an HTTP 429 status code (TooManyRequests).
On average, for 10 requests, 2 will return 429 response and the remaining 8 return the correct value.
It also happened to me that when it was the first request (so there is no option for TooManyRequests)
public static List<ResponseObject> GetProductsFromRestAPI(int[] ProductIdArray )
{
List<ResponseObject> products = new List<ResponseObject>();
string action;
for (int i = 0; i < ProductIdArray.Length; i++)
{
action = "products/" + ProductIdArray[i].ToString();
client = AddHeadersToClient(action, new RestClient("https://api.usedbythiscode.com/")); //required by this API.
var currentProduct = RequestsLib.GetProduct(client, action);
products.Add(currentProduct);
}
return products;
}
public static Product GetProduct(RestClient restClient, string action) //todo test this for bugs
{
var result = new Product();
var request = new RestRequest(action, Method.GET);
var response = SendRequest(restClient, request);//Here I sometimes get response with 429.
//.. Other code
return result;
}
public static async Task<IRestResponse> SendRequest(RestClient restClient, RestRequest request)
{
return await restClient.ExecuteGetAsync(request);
}
Temporarily resolved it by sending another request with do while loop and usually second request return right answer.
do
{
SendRequest(restClient, request);
}
while (StatusCode != 200);
Where could the cause of the error lie?
Is it possible that I have unclosed requests?
Is creating multiple RestSharp clients a good practice?
EDIT:
The problem was on the server side. All I had to do was report the bug to the admins who provided the API. Thank you for help.
429 is Too Many Requests. Most APIs have some kind of rate-limiting in place so that a single client can't take down their server with too many requests.
The proper response for 429 is to retry. I recommend using Polly for retry logic, but be aware that HandleTransientHttpError doesn't consider 429 a transient error.
I agree with #mason, you should use async method with Task<> and await response Here is the part of login side of my mobileApp-project in Xamarin. You may want to see how to use async with Task<> easily.
public async Task<BSUser> ValidateUser(string userName, string password)
{
string url = Xamarin.Essentials.Preferences.Get(Constants.URL_KEY, "") + "/api/Validateuser";
HttpClient _Client = new HttpClient();
var data = new Dictionary<string, string>
{
{"userName", userName},
{"password", password}
};
string jsonData = JsonConvert.SerializeObject(data);
HttpContent content = new StringContent(jsonData, Encoding.UTF8, "application/json");
try
{
HttpResponseMessage httpResponse = await _Client.PostAsync(url, content);
if (httpResponse.IsSuccessStatusCode)
{
try {
var responseData = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
var result = JsonConvert.DeserializeObject(responseData).ToString();
UserInfo userInfo = JsonConvert.DeserializeObject<UserInfo>(result);
BSUser value = new BSUser();
value.UserName = userInfo.userCode;
return value;
}
catch (Java.Net.SocketException e)
{
Console.WriteLine("Hata", e);
return null;
}
}
else
{
return null;
}
}
catch (SystemException)
{
return null;
}
}

third party API returns httpresponsemessage. can someone help me to understand how to extract data out of httpresponsemessage and return generic type

public async Task<>GetLandProperty(string num)
{
var request = new HttpRequestMessage(HttpMethod.Get, _httpClient.BaseAddress+relativeurl);
// added required headers here.
var response = await _httpClient.SendAsync(request);
}
now here response is httpresponsemessage. i dont understand how to extract data out of response and deserialize it. probably i can create class, fetch response's content and deserialise it but what to do if it return failure response or exception. how to return generic type in this case
This is related to your api code. You can catch the exception information and return different information by setting the HttpStatusCode of HttpResponseMessage in the api side.
In the GetLandProperty method, you can set the return type to object to return different content.
Determine the returned content by judging the status of IsSuccessStatusCode of response. If the status is true, use the type returned by deserialize and return the corresponding object. If it is false, return the response directly.
public async Task<object> GetLandProperty()
{
Product product = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("url");
var request = new HttpRequestMessage(HttpMethod.Get, client.BaseAddress);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string responseString = response.Content.ReadAsStringAsync().Result;
Newtonsoft.Json.Linq.JObject json = Newtonsoft.Json.Linq.JObject.Parse(responseString);
product = Newtonsoft.Json.JsonConvert.DeserializeObject<Product>(responseString);
return product;
}
return response;
}
}
My api code:
[HttpGet]
public HttpResponseMessage Get(int id)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
Product persona = _context.Product.FirstOrDefault(p => p.DataID == id);
if (persona != null)
{
response.Content = new StringContent(JsonConvert.SerializeObject(persona));
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return response;
}
else
{
response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Content = new StringContent("error message");
return response;
}
}
Update
Another method to receive GetLandProperty method returned content:
public async Task<object> CallTest()
{
var obj = await GetLandProperty();
if(obj is Product)
{
Product product= (Product)obj;
// save data to db
}
else
{
//do other operations
return obj;
}
}
Here is the test result by postman:
I am not very sure whats the question.
To get response data
var result = await response.Content.ReadAsStringAsync();
and to desriazile it
var json= JsonConvert.DeserializeObject<T>(result);

Windows Phone 8.1 - JSON (REST post/get/put) requests and their de/serialization

I have multiple REST requests, where I want to get/post/put data in JSON format with different objects for each request.
However I do not want to repeat myself by writing call to the server and deserealization method for each request. However each request or response contains different object to de/serialize, how to write general method for calling and parsing it?
(note this is self-answered question, I want to share my help-request classes which are easy to use)
For using same method for different class types, you have to use Generic class. With that, you can set up custom de/serialization.
This is class for sending and receiving GET request :
public class JsonGet<O>
{
public async Task<O> DoGetRequest(string url)
{
var client = new HttpClient();
CultureInfo ci = new CultureInfo(Windows.System.UserProfile.GlobalizationPreferences.Languages[0]);
client.DefaultRequestHeaders.Add("Accept-Language", ci.TwoLetterISOLanguageName);
var uri = new Uri(string.Format(
url,
"action",
"get",
DateTime.Now.Ticks
));
var response = client.GetAsync(uri);
HttpResponseMessage x = await response;
if (x.StatusCode != System.Net.HttpStatusCode.OK)
{
//throw new ConnectionOutException("While posting: " + url + " we got the following status code: " + x.StatusCode);
}
HttpContent requestContent = x.Content;
string jsonContent = requestContent.ReadAsStringAsync().Result;
JsonConvert.DeserializeObject<O>(jsonContent);
return JsonConvert.DeserializeObject<O>(jsonContent);
}
}
Then you can easily get deserialized object by following. Note that the class you type into generic part (in this case it is class Defaults), is the type to be deserealized :
JsonGet<Defaults> get = new JsonGet<Defaults>();
Defaults myMethod = await get.DoGetRequest(Constants.Constants.UrlDefaults);
Very similarly, you can have class for PUT/POST requests
public class JsonSend<I, O>
{
public async Task<O> DoPostRequest(string url, I input)
{
var client = new HttpClient();
CultureInfo ci = new CultureInfo(Windows.System.UserProfile.GlobalizationPreferences.Languages[0]);
client.DefaultRequestHeaders.Add("Accept-Language", ci.TwoLetterISOLanguageName);
var uri = new Uri(string.Format(
url,
"action",
"post",
DateTime.Now.Ticks
));
string serialized = JsonConvert.SerializeObject(input);
StringContent stringContent = new StringContent(
serialized,
Encoding.UTF8,
"application/json");
var response = client.PostAsync(uri, stringContent);
HttpResponseMessage x = await response;
if (x.StatusCode != System.Net.HttpStatusCode.OK)
{
//throw new ConnectionOutException("While puting: " + url + " we got the following status code: " + x.StatusCode);
}
HttpContent requestContent = x.Content;
string jsonContent = requestContent.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<O>(jsonContent);
}
public async Task<O> DoPutRequest(string url, I input)
{
var client = new HttpClient();
CultureInfo ci = new CultureInfo(Windows.System.UserProfile.GlobalizationPreferences.Languages[0]);
client.DefaultRequestHeaders.Add("Accept-Language", ci.TwoLetterISOLanguageName);
var uri = new Uri(string.Format(
url,
"action",
"put",
DateTime.Now.Ticks
));
var response = client.PutAsync(uri,
new StringContent(
JsonConvert.SerializeObject(input),
Encoding.UTF8,
"application/json"));
HttpResponseMessage x = await response;
if (x.StatusCode != System.Net.HttpStatusCode.OK)
{
//throw new ConnectionOutException("While puting: " + url + " we got the following status code: " + x.StatusCode);
}
HttpContent requestContent = x.Content;
string jsonContent = requestContent.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<O>(jsonContent);
}
}
And then you can send and receive POST as following :
JsonSend<User, UserReceived> register = new JsonSend<User, UserReceived>();
UserReceived responseUser = await register.DoPostRequest(Constants.Constants.UrlRegister, user);
you can try this generic menthod
public static async Task<T> ExecuteGet<T, K>(string url, K obj) where T : class
{
if (String.IsNullOrWhiteSpace(url))
return default(T);
var client = new HttpClient();
string str = JsonConvert.SerializeObject(obj);
Debug.WriteLine("json Request :" + url + str);
HttpResponseMessage response = await client.GetAsync(new Uri(url + str));
if (response.IsSuccessStatusCode)
{
var ResponceString = await response.Content.ReadAsStringAsync();
Debug.WriteLine("Json responce :" + ResponceString);
var data = JsonConvert.DeserializeObject<T>(ResponceString);
return (T)Convert.ChangeType(data, typeof(T));
}
else
{
return default(T);
}
}

How to use System.Net.Http.HttpClient to GET after POST with AllowAutoRedirect = true

Here is the code to create a client and POST an object. It is my understanding that setting AllowAutoRedirect = true would enable the ability for the client to follow the redirect then do a GET and I would be able to deserialize said object. My testing has proven unsuccessful so far. Is there something that I may have overlooked?
Web API endpoint:
public HttpResponseMessage Post([FromBody] Contact contact) {
try {
// Add user
...
var msg = Request.CreateResponse(HttpStatusCode.Created);
msg.Headers.Location = new Uri(Request.RequestUri + "/" + customer.Person.PersonID);
return msg;
} catch (ValidationException tve) {
var apiError = new ApiResponseMessage { Message = "Invalid contact" };
foreach (var message in FilterErrors(tve.Messages)) {
if (message.Contains("required", StringComparison.OrdinalIgnoreCase)) {
apiError.Errors.Add(new ApiErrorMessage {
Code = ErrorCode.RequiredPropertyNotProvided,
Message = message
});
} else {
apiError.Errors.Add(new ApiErrorMessage {
Code = ErrorCode.PropertyNotValid,
Message = message
});
}
}
return Request.CreateResponse(HttpStatusCode.BadRequest, apiError);
}
}
Client code:
public Contact Post(Contact contact)
{
try
{
var handler = new HttpClientHandler { AllowAutoRedirect = true};
using (var client = new HttpClient(handler))
{
client.BaseAddress = new Uri(APIServer);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-ApiKey", APIKey.ToString());
var response = client.PostAsJsonAsync("v1/Contacts", contact).Result;
if (response.IsSuccessStatusCode)
{
Log.DebugFormat("Post v1/Contacts => {0} ({1})", response.StatusCode, response.ReasonPhrase);
var result = JsonConvert.DeserializeObject<Contact>(response.Content.ReadAsStringAsync().Result);
// This object is null
}
else
{
Log.ErrorFormat("Post v1/Contacts => {0} ({1})", response.StatusCode, response.ReasonPhrase);
var result = JsonConvert.DeserializeObject<ApiMessageResponse>(
response.Content.ReadAsStringAsync().Result);
}
}
}
catch (Exception exception)
{
Log.Error(exception);
}
return null;
}
Wireshark logs.
POST /v1/Contacts HTTP/1.1 (application/json)
HTTP/1.1 201 Created
Location: http://api01.example.com/v1/Contacts/10135052
and that's it, no GET (http://api01.example.com/v1/Contacts/10135052) after as far as I can tell.
From the log trace you added, it looks like the response from the POST is a 201 (Created); there is no redirect. The response does contain a URL (either in a header or the body, hard to tell) but it will not mean anything special. Your client will need to parse the URL itself and issue the subsequent GET request explicitly.

How to POST request using RestSharp

I m trying to POST the request using RestSharp client as follows
I m passing the Auth Code to following function
public void ExchangeCodeForToken(string code)
{
if (string.IsNullOrEmpty(code))
{
OnAuthenticationFailed();
}
else
{
var request = new RestRequest(this.TokenEndPoint, Method.POST);
request.AddParameter("code", code);
request.AddParameter("client_id", this.ClientId);
request.AddParameter("client_secret", this.Secret);
request.AddParameter("redirect_uri", "urn:ietf:wg:oauth:2.0:oob");
request.AddParameter("grant_type", "authorization_code");
request.AddHeader("content-type", "application/x-www-form-urlencoded");
client.ExecuteAsync<AuthResult>(request, GetAccessToken);
}
}
void GetAccessToken(IRestResponse<AuthResult> response)
{
if (response == null || response.StatusCode != HttpStatusCode.OK
|| response.Data == null
|| string.IsNullOrEmpty(response.Data.access_token))
{
OnAuthenticationFailed();
}
else
{
Debug.Assert(response.Data != null);
AuthResult = response.Data;
OnAuthenticated();
}
}
But i am getting response.StatusCode = Bad Request. Can anyone help me for how do i POST the request using Restsharp client.
My RestSharp POST method:
var client = new RestClient(ServiceUrl);
var request = new RestRequest("/resource/", Method.POST);
// Json to post.
string jsonToSend = JsonHelper.ToJson(json);
request.AddParameter("application/json; charset=utf-8", jsonToSend, ParameterType.RequestBody);
request.RequestFormat = DataFormat.Json;
try
{
client.ExecuteAsync(request, response =>
{
if (response.StatusCode == HttpStatusCode.OK)
{
// OK
}
else
{
// NOK
}
});
}
catch (Exception error)
{
// Log
}
This way works fine for me:
var request = new RestSharp.RestRequest("RESOURCE", RestSharp.Method.POST) { RequestFormat = RestSharp.DataFormat.Json }
.AddBody(BODY);
var response = Client.Execute(request);
// Handle response errors
HandleResponseErrors(response);
if (Errors.Length == 0)
{ }
else
{ }
Hope this helps! (Although it is a bit late)
As of 2017 I post to a rest service and getting the results from it like that:
var loginModel = new LoginModel();
loginModel.DatabaseName = "TestDB";
loginModel.UserGroupCode = "G1";
loginModel.UserName = "test1";
loginModel.Password = "123";
var client = new RestClient(BaseUrl);
var request = new RestRequest("/Connect?", Method.POST);
request.RequestFormat = DataFormat.Json;
request.AddBody(loginModel);
var response = client.Execute(request);
var obj = JObject.Parse(response.Content);
LoginResult result = new LoginResult
{
Status = obj["Status"].ToString(),
Authority = response.ResponseUri.Authority,
SessionID = obj["SessionID"].ToString()
};
it is better to use json after post your resuest like below
var clien = new RestClient("https://smple.com/");
var request = new RestRequest("index", Method.POST);
request.AddHeader("Sign", signinstance);
request.AddJsonBody(JsonConvert.SerializeObject(yourclass));
var response = client.Execute<YourReturnclassSample>(request);
if (response.StatusCode == System.Net.HttpStatusCode.Created)
{
return Ok(response.Content);
}
I added this helper method to handle my POST requests that return an object I care about.
For REST purists, I know, POSTs should not return anything besides a status. However, I had a large collection of ids that was too big for a query string parameter.
Helper Method:
public TResponse Post<TResponse>(string relativeUri, object postBody) where TResponse : new()
{
//Note: Ideally the RestClient isn't created for each request.
var restClient = new RestClient("http://localhost:999");
var restRequest = new RestRequest(relativeUri, Method.POST)
{
RequestFormat = DataFormat.Json
};
restRequest.AddBody(postBody);
var result = restClient.Post<TResponse>(restRequest);
if (!result.IsSuccessful)
{
throw new HttpException($"Item not found: {result.ErrorMessage}");
}
return result.Data;
}
Usage:
public List<WhateverReturnType> GetFromApi()
{
var idsForLookup = new List<int> {1, 2, 3, 4, 5};
var relativeUri = "/api/idLookup";
var restResponse = Post<List<WhateverReturnType>>(relativeUri, idsForLookup);
return restResponse;
}
It's better to specify the Json data body.
var client = new RestClient("URL");
var request = new RestRequest("Resources",Method.POST);
request.RequestFormat = RestSharp.DataFormat.Json;
request.AddBody(new classname
{
id = value1;
Name = "";
});
var response = client.Execute(request);
Check statusCode from response.
Always ensure that status code should be OK.

Categories

Resources