How to read webapi responses with HttpClient in C# - c#

I have developed a small webapi which has a few actions and returns my custom class called Response.
The Response class
public class Response
{
bool IsSuccess=false;
string Message;
object ResponseData;
public Response(bool status, string message, object data)
{
IsSuccess = status;
Message = message;
ResponseData = data;
}
}
My webapi with actions
[RoutePrefix("api/customer")]
public class CustomerController : ApiController
{
static readonly ICustomerRepository repository = new CustomerRepository();
[HttpGet, Route("GetAll")]
public Response GetAllCustomers()
{
return new Response(true, "SUCCESS", repository.GetAll());
}
[HttpGet, Route("GetByID/{customerID}")]
public Response GetCustomer(string customerID)
{
Customer customer = repository.Get(customerID);
if (customer == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return new Response(true, "SUCCESS", customer);
//return Request.CreateResponse(HttpStatusCode.OK, response);
}
[HttpGet, Route("GetByCountryName/{country}")]
public IEnumerable<Customer> GetCustomersByCountry(string country)
{
return repository.GetAll().Where(
c => string.Equals(c.Country, country, StringComparison.OrdinalIgnoreCase));
}
}
Now where I am stuck is that I do not know how to read the response data returned from the webapi actions and extract json from my response class. After getting json how could I deserialize that json to the customer class.
This is the way I am calling my webapi function:
private void btnLoad_Click(object sender, EventArgs e)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:8010/");
// Add an Accept header for JSON format.
//client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// List all Names.
HttpResponseMessage response = client.GetAsync("api/customer/GetAll").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
Console.WriteLine("Request Message Information:- \n\n" + response.RequestMessage + "\n");
Console.WriteLine("Response Message Header \n\n" + response.Content.Headers + "\n");
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
Console.ReadLine();
}
Questions
How to get the response class the webapi returns at the client side
How could I extract json from the response class
How to deserialize the json to the customer class at client side
I use this code but still getting an error.
var baseAddress = "http://localhost:8010/api/customer/GetAll";
using (var client = new HttpClient())
{
using (var response = client.GetAsync(baseAddress).Result)
{
if (response.IsSuccessStatusCode)
{
var customerJsonString = await response.Content.ReadAsStringAsync();
var cust = JsonConvert.DeserializeObject<Response>(customerJsonString);
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
}
}
The error is:
An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code
Additional information: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'WebAPIClient.Response[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
Why is the response causing this error?

On the client, include a read of the content:
HttpResponseMessage response = client.GetAsync("api/customer/GetAll").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
Console.WriteLine("Request Message Information:- \n\n" + response.RequestMessage + "\n");
Console.WriteLine("Response Message Header \n\n" + response.Content.Headers + "\n");
// Get the response
var customerJsonString = await response.Content.ReadAsStringAsync();
Console.WriteLine("Your response data is: " + customerJsonString);
// Deserialise the data (include the Newtonsoft JSON Nuget package if you don't already have it)
var deserialized = JsonConvert.DeserializeObject<IEnumerable<Customer>>(custome‌​rJsonString);
// Do something with it
}
Change your WebApi not to use your Response class but rather an IEnumerable of Customer. Use the HttpResponseMessage response class.
Your WebAPI should only require:
[HttpGet, Route("GetAll")]
public IEnumerable<Customer> GetAllCustomers()
{
var allCustomers = repository.GetAll();
// Set a breakpoint on the line below to confirm
// you are getting data back from your repository.
return allCustomers;
}
Added code for a generic response class based on the discussion in the comments although I still recommend you don't do this and avoid calling your class Response. You should rather return HTTP status codes instead of your own. A 200 Ok, a 401 Unauthorised, etc. Also this post on how to return HTTP status codes.
public class Response<T>
{
public bool IsSuccess { get; set; }
public string Message { get; set; }
public IEnumerable<T> ResponseData { get; set; }
public Response(bool status, string message, IEnumerable<T> data)
{
IsSuccess = status;
Message = message;
ResponseData = data;
}
}

OR you can convert on same call
TResponse responseobject = response.Content.ReadAsAsync<TResponse>().Result;
responseJson += "hostResponse: " + JsonParser.ConvertToJson(responseobject);
//_logger.Debug($"responseJson : {responseJson}", correlationId);

Related

Serialize async response from a database client

I have an endpoint which is used to create an item. The controller calls the service which creates the item, makes some changes on the db and db returns data based on the procedure. The db returns a json like response, but is not always the same, so I have to adjust on the backend so that I can formalize the response type.
The problem is that create item service is asynchronous and I need to be able to await the response so I can make a new response based on that. How can I await the response and that I get from db client and then return data based on that.
This is my Action and I want to be able to serialize async response from service
[HttpPost]
public IActionResult CreateItem([FromBody] InputModel item)
{
var jsonString = _itemService.CreateItem(item);
ResponseModel? response = JsonSerializer.Deserialize<ResponseModel>(jsonString);
return new ObjectResult(response.Response) { StatusCode = response.StatusCode };
}
The default response model
public class ResponseModel
{
public string Response { get; set; }
public int StatusCode { get; set; }
}
Create Item service, which makes the post request to the client and it has to be async.
Depending on the status code that is coming from the client, I want to be able to set my action status code as well.
public async Task<string> CreateItem(InputModel item)
{
if (item.VersionType != 1)
{
return new { Response = "Incorrect data", StatusCode = 400 }.ToString()!;
}
var json = JsonSerializer.Serialize(item);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, data);
var content = await response.Content.ReadAsStringAsync();
return content;
}
You can make your action method async and then await the method call _itemService.CreateItem for it :
[HttpPost]
public async Task<IActionResult> CreateItem([FromBody] InputModel item)
{
var jsonString = await _itemService.CreateItem(item);
ResponseModel? response = JsonSerializer.Deserialize<ResponseModel>(jsonString);
return new ObjectResult(response.Response) { StatusCode = response.StatusCode };
}
Now your action method would asyncrounously wait for the result from CreateItem and when it returs result, it will continue executing further and send the deserialized response back to client.

How to return the same status code from a second API call

I have an ASP.NET Core API calling a second API.
I throw an exception in my services layer, if there is an error from the second API:
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
//return HTTP response with StatusCode = X, if response.StatusCode == X
throw new HttpRequestException(await response.Content.ReadAsStringAsync());
//this always returns 400
}
How can I throw an exception that will return a response with the same status code from the second API call?
If I use HttpRequestException it will always return 400, even if the response object had StatusCode = 500.
EDIT:
The first API endpoint looks like this:
public async Task<ActionResult<HttpResponseMessage>> CreateTenancy([FromBody]TenancyRequest tenancy)
{
//Make some calls...
return Created(string.Empty, new { TenancyID = newTenancyExternalId });
}
The second API endpoint looks like this:
[HttpPost]
public IHttpActionResult CreateTenancy([FromBody]TenancyDTO tenancyDTO)
{
var tenancy = GetTenancy();
return Created(string.Empty, tenancy);
}
I've tried using throw new HttpResponseException(response); but this removes the descriptive Exception message, the payload ends up like this:
{
"Code": 500,
"CorrelationId": "2df08016-e5e3-434a-9136-6824495ed907",
"DateUtc": "2020-01-30T02:02:48.4428978Z",
"ErrorMessage": "Processing of the HTTP request resulted in an exception. Please see the HTTP response returned by the 'Response' property of this exception for details.",
"ErrorType": "InternalServerError"
}
I'd like to keep the ErrorMessage value in the original payload:
{
"Code": 400,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "BadRequest"
}
The end goal is to have this returned:
{
"Code": 500,
"CorrelationId": "ff9466b4-8c80-4dab-b5d7-9bba1355a567",
"DateUtc": "2020-01-30T03:05:13.2397543Z",
"ErrorMessage": "\"Specified cast is not valid.\"",
"ErrorType": "InternalServerError"
}
I tried something simple as changing the return type of the API endpoint and returning the object as it when there is an error. Otherwise, build your own HttpResponseMessage and return that. This snippet below uses text but you can use a serializer to serialize other content if you have.
public async Task<HttpResponseMessage> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"myAPI that returns different errors 400, 404, 500 etc based on str");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return response;
// do something else
return new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent("Your Text here") };
}
Other approach of using Filters
The other approach of using IHttpActionResult as your return type, you can use Filters to conform all your HttpResponseMessages to IHttpActionResult.
Filter: Create a separate cs file and use this filter definition.
public class CustomObjectResponse : IHttpActionResult
{
private readonly object _obj;
public CustomObjectResponse(object obj)
{
_obj = obj;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = _obj as HttpResponseMessage;
return Task.FromResult(response);
}
}
and in your API, you would use your filter like so,
public async Task<IHttpActionResult> Test(string str)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, $"http://localhost:4500/api/capacity/update-mnemonics/?mnemonic_to_update={str}");
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
return new CustomObjectResponse(response);
// Other Code here
// Return Other objects
KeyValuePair<string, string> testClass = new KeyValuePair<string, string>("Sheldon", "Cooper" );
return new OkWithObjectResult(testClass);
// Or Return Standard HttpResponseMessage
return Ok();
}
You could simply make your API call and copy its response code into something compatible with IStatusCodeActionResult.
An alternative s to throw a custom exception. Create something like
public class ApiCallException : Exception
{
public APiCallException(int statusCode, ...)
{
ApiStatusCode = statusCode;
}
int ApiStatusCode { get; }
...
}
and copy over the status code from your API result, and then throw the exception.
var response = await httpClient.SendAsync(request); //call second API
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
throw new ApiCallException(500, content);
}
You can then register an exception filter to deal with the result when calling AddMvc.
services.AddMvc(options => options.Filters.Add<ExceptionFilter>());
where ExceptionFilter could be something like
public class ExceptionFilter : IExceptionFilter
{
// ...
public void OnException(ExceptionContext context)
{
if (context.Exception is ApiCallException ace)
{
var returnObject = CreateReturnObjectSomehow();
context.Result = new ObjectResult(returnObject) { StatusCode = ace.StatusCode };
}
else
{
// do something else
}
}
}
Thanks Jawad and Kit for providing great answers which helped me work out the solution below:
Turns out there was some middleware handling the exception:
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception exception)
{
if (httpContext.Response.HasStarted) throw;
var statusCode = ConvertExceptionToHttpStatusCode(exception);
httpContext.Response.Clear();
httpContext.Response.StatusCode = (int)statusCode;
httpContext.Response.ContentType = "application/json";
if (statusCode != HttpStatusCode.BadRequest)
{
_logger.Error(exception, "API Error");
}
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(new Error(statusCode, httpContext.Request.CorrelationId(), exception.Message, statusCode.ToString())));
}
}
The Error class looks like this:
public class Error
{
public int Code { get; }
public Guid? CorrelationId { get; }
public DateTime DateUtc { get; }
public string ErrorMessage { get; }
public string ErrorType { get; }
public Error(HttpStatusCode code, Guid? correlationId, string errorMessage, string errorType)
{
Code = (int)code;
CorrelationId = correlationId;
DateUtc = DateTime.UtcNow;
ErrorMessage = errorMessage;
ErrorType = errorType;
}
}
I created this class:
public class ApiCallException : Exception
{
public int StatusCode { get; }
public override string Message { get; }
public ApiCallException(int statusCode, string message)
{
StatusCode = statusCode;
Message = message;
}
}
Then updated my original code to have this:
if (!response.IsSuccessStatusCode)
{
throw new ApiCallException((int)response.StatusCode, await response.Content.ReadAsStringAsync());
}

Show message to user in Xamarin API

You have an API (NOT modifiable) which you want to consume, this API receives some parameters which if they are NOT properly validated, the API throws a message of the error, it is precisely this message that I want to capture, for example here in the image I pass an erroneous password and want to show that message to the user.
For this, create a class called Response, which is responsible for managing the different calls made to the API
Response.cs:
public class Response
{
public bool IsSuccess { get; set; }
public string Message { get; set; }
public object Result { get; set; }
[JsonProperty(PropertyName = "userMessage")]
public string userMessage { get; set; }
}
in my LoginViewModel I call the method that this API consumes, which is implemented in a class called ApiService.cs:
ApiService.cs:
public async Task<Response> GetLogin(
string urlAPILogin, string KeyLogin, string Rut, string Password)
{
try
{
var client = new HttpClient();
client.BaseAddress = new Uri(urlAPILogin);
string url = string.Format("login/index/?k=" + KeyLogin + "&rut=" + Rut + "&password=" + Password);
var response = await client.GetAsync(url);
var result = await response.Content.ReadAsStringAsync();
var model = JsonConvert.DeserializeObject<Response>(result);
if (!response.IsSuccessStatusCode)
{
return new Response
{
IsSuccess = false,
Message = response.StatusCode.ToString(),
Result = model,
};
}
return new Response
{
IsSuccess = true,
Message = "Ok",
Result = model,
};
}
catch (Exception ex)
{
return new Response
{
IsSuccess = false,
Message = ex.Message,
};
}
}
Now it is in my ViewModel (LoginViewModel) where I want to paint that message! and I try to capture it in the following way:
var response = await apiService.GetLogin(
urlAPILogin,
KeyLogin,
Rut,
Password);
if (string.IsNullOrEmpty(response.userMessage))
{
IsRunning = false;
IsEnabled = true;
await dialogService.ShowMessage(
"Error",
response.userMessage);
Password = null;
return;
}
but I'm not getting the expected response (he paints the blank message to me !!!)
being that the object if it brings the message !!
any help for me?
what am I doing wrong?
In your LoginViewModel method where you are awaiting the response, the variable with the userMessage you want is located a layer deeper.
Currently:
// userMessage is null in your *locals* section
await dialogService.ShowMessage("Error", response.userMessage);
Should be:
// This is where the wanted value is: userMessage is the desired message
await dialogService.ShowMessage("Error", response.Result.userMessage);
You will need to cast your response.Result as a Response since your Result variable is an object, but this should fix your problem.
Should be (casted):
// This is where the wanted value is: userMessage is the desired message
await dialogService.ShowMessage("Error", (response.Result as Response)?.userMessage);

Error handling (Sending ex.Message to the client)

I have an ASP.NET Core 1.0 Web API application and trying to figure out how to pass the exception message to the client if a function that my controller is calling errors out.
I have tried so many things, but nothing implements IActionResult.
I don't understand why this isn't a common thing that people need. If there truthfully is no solution can someone tell me why?
I do see some documentation out there using HttpResponseException(HttpResponseMessage), but in order to use this, I have to install the compat shim. Is there a new way of doing these things in Core 1.0?
Here is something I have been trying with the shim but it isn't working:
// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
Customer c = _customersService.GetCustomerById(id);
if (c == null)
{
var response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent("Customer doesn't exist", System.Text.Encoding.UTF8, "text/plain"),
StatusCode = HttpStatusCode.NotFound
};
throw new HttpResponseException(response);
//return NotFound();
}
return new ObjectResult(c);
}
When the HttpResponseException is thrown, I look on the client and can't find the message I am sending anything in the content.
Here is an simple error DTO class
public class ErrorDto
{
public int Code {get;set;}
public string Message { get; set; }
// other fields
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
And then using the ExceptionHandler middleware:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = 500; // or another Status accordingly to Exception Type
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorDto()
{
Code = <your custom code based on Exception Type>,
Message = ex.Message // or your custom message
// other custom data
}.ToString(), Encoding.UTF8);
}
});
});
Yes it is possible to change the status code to whatever you need:
In your CustomExceptionFilterAttribute.cs file modify the code as follows:
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
context.Result = new ContentResult
{
Content = $"Error: {exception.Message}",
ContentType = "text/plain",
// change to whatever status code you want to send out
StatusCode = (int?)HttpStatusCode.BadRequest
};
}
}
That's pretty much it.
If you have custom exceptions, then you can also check for them when grabbing the thrown exception from the context. Following on from that you can then send out different HTTP Status Codes depdending on what has happened in your code.
Hope that helps.
You can create a custom Exception Filter like below
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
context.Result = new JsonResult(exception.Message);
}
}
Then apply the above attribute to your controller.
[Route("api/[controller]")]
[CustomExceptionFilter]
public class ValuesController : Controller
{
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
throw new Exception("Suckers");
return new string[] { "value1", "value2" };
}
}
Rather than raising and catching an exception, how about you simplify your action to:
// GET: api/customers/{id}
[HttpGet("{id}", Name = "GetCustomer")]
public IActionResult GetById(int id)
{
var customer = _customersService.GetCustomerById(id);
if (customer == null)
{
return NotFound("Customer doesn't exist");
}
return Ok(customer);
}
I wrote a blog post with some more options such as returning a JSON object instead of text.
Maybe that is helpful. You can return just object and sent for example a BadRequest (HTTP CODE: 400) with your custom object as actual parameter (I just used an interpolated string here) but you can put in anything.
In your client side you can catch that error situation for example with an AJAX error handler.
// GET: api/TruckFahrerGeoData
[HttpGet]
public object GetTruckFahrerGeoData()
{
var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();
var geodataItems = _context.TruckFahrerGeoData;
foreach (var truckFahrerGeoData in geodataItems)
{
GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);
if (geoTelemetryData == null)
{
return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
}
TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
{
Speed = geoTelemetryData.Speed,
Accuracy = geoTelemetryData.Accuracy,
TruckAppId = geoTelemetryData.Activity.TruckAppId,
TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
ClId = geoTelemetryData.Activity.ClId,
TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
TaskId = geoTelemetryData.Activity.TaskId,
TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
};
truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
}
return truckFahrerGeoDataItems;
}
Or an even more cleaner way with IActionResult like that way:
// GET: api/TruckFahrerGeoData
[HttpGet]
public IActionResult GetTruckFahrerGeoData()
{
var truckFahrerGeoDataItems = new List<TruckFahrerGeoDataViewModel>();
var geodataItems = _context.TruckFahrerGeoData;
foreach (var truckFahrerGeoData in geodataItems)
{
GeoTelemetryData geoTelemetryData = JsonConvert.DeserializeObject<GeoTelemetryData>(truckFahrerGeoData.TelemetryData);
if (geoTelemetryData == null)
{
return BadRequest($"geoTelemetryData null for id: {truckFahrerGeoData.Id}");
}
TruckFahrerGeoDataViewModel truckFahrerGeoDataViewModel = new TruckFahrerGeoDataViewModel
{
Speed = geoTelemetryData.Speed,
Accuracy = geoTelemetryData.Accuracy,
TruckAppId = geoTelemetryData.Activity.TruckAppId,
TruckAuftragStatusId = geoTelemetryData.Activity.TruckAuftragStatusId,
ClId = geoTelemetryData.Activity.ClId,
TruckAuftragLaufStatusId = geoTelemetryData.Activity.TruckAuftragLaufStatusId,
TaskId = geoTelemetryData.Activity.TaskId,
TruckAuftragWorkflowStatusId = geoTelemetryData.Activity.TruckAuftragWorkflowStatusId
};
truckFahrerGeoDataItems.Add(truckFahrerGeoDataViewModel);
}
return Ok(truckFahrerGeoDataItems);
}
Late to the party but refining the answer .
Define your error response class with minimum below attributes
using Microsoft.AspNetCore.Http;
public class ErrorResponse
{
private readonly RequestDelegate next;
public ErrorResponse(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context )
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError;
string result = string.Empty;
object data = new object();
if (ex is ForbiddenException)
{
code = HttpStatusCode.Forbidden;
result = JsonConvert.SerializeObject(new Response<object>(Status.Forbidden(ex.Message), data));
}
else if(ex is BadRequestException){
code = HttpStatusCode.BadRequest;
result = JsonConvert.SerializeObject(new Response<object>(Status.BadRequest(ex.Message), data));
}
else if (ex is NotFoundException)
{
code = HttpStatusCode.NotFound;
result = JsonConvert.SerializeObject(new Response<object>(Status.NotFound(ex.Message), data));
}
else if (ex is UnauthorizedException)
{
code = HttpStatusCode.Unauthorized;
result = JsonConvert.SerializeObject(new Response<object>(Status.Unauthorized(ex.Message), data));
}
else
{
result = JsonConvert.SerializeObject(new Response<object>(Status.InternalServerError(ex.Message), data));
}
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
Next use this class as middleware in startup.cs class
app.UseHttpsRedirection();
app.UseMiddleware(typeof(ErrorResponse));
Now each request and response will go through this class,if an error occurs then error code will be set to true with error code. A sample response like below
data: {}
status: {
code: 404
error: true
message: "No employee data found"
type: "Not Found"
}
I had the same problem and after some research, I found out I could use HttpClient to call my API and read the response easily. HttpClient does not throw any error when the HTTP response contains an error code, but it sets the IsSuccessStatusCode property to false.
This is my function using the HttpClient. I call this from my controller.
public static async Task<HttpResponseMessage> HttpClientPost(string header, string postdata, string url)
{
string uri = apiUrl + url;
using (var client = new HttpClient())
{
//client.BaseAddress = new Uri(uri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", header);
HttpResponseMessage response = await client.PostAsync(uri, new StringContent(postdata));
return response;
}
}
This is my controller code, where I call the function and read the response and determine whether I have an error or not and respond accordingly. Note that I am checking the IsSuccessStatusCode.
HttpResponseMessage response;
string url = $"Setup/AddDonor";
var postdata = JsonConvert.SerializeObject(donor);
response = await ApiHandler.HttpClientPost(HttpContext.Session.GetString(tokenName), postdata, url);
//var headers = response.Headers.Concat(response.Content.Headers);
var responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
tnxresult = JsonConvert.DeserializeObject<TnxResult>(AppFunctions.CleanResponse(responseBody));
return Json(new
{
ok = true,
message = tnxresult.Message,
statusCode = tnxresult.StatusCode
});
}
else
{
ApiError rs = JsonConvert.DeserializeObject<ApiError>(AppFunctions.CleanResponse(responseBody));
return Json(new
{
ok = false,
message = rs.Message,
statusCode = rs.StatusCode
});
}
My API returns error messages in JSON. If the call is successful, I am packing the response in JSON too.
The crucial line of code is this one...
var responseBody = await response.Content.ReadAsStringAsync();
It serializes the HTTP content to a string as an asynchronous operation.
After that I can convert my JSON string to an object and access the error/success message and the Status Code too.

Converting the content of HttpResponseMessage to object

My Question: How do I do this?
So, I hadn't touched anything .Net in about 6 years until this week. There's a lot that I've forgotten and even more that I never knew and while I love the idea of the async/await keywords, I'm having a slight problem implementing the following requirements for a client's API implementation:
The ServerAPI class has a method for each of the API methods, taking appropriate input parameters (e.g. the method Login takes in an id and a password, makes the API call and returns the result to the caller).
I want to abstract away the JSON so that my API methods return the actual object you're fetching (e.g. the Login method above returns a User object with your auth token, uid, etc.)
Some API methods return a 204 on success or no meaningful content (not meaningful in my usecase maybe I only care about success/failure), for these I'd like to return either a bool (true = success) or the status code.
I'd like to keep the async/await (or equivalent) design, because it seems to really work well so far.
For some methods, I might need to just return the HttpResponseMessage object and let the caller deal with it.
This is roughly what I have so far and I'm not sure how to make it compliant with the above OR whether I'm even doing this right. Any guidance is appreciated (flaming, however, is not).
// 200 (+User JSON) = success, otherwise APIError JSON
internal async Task<User> Login (string id, string password)
{
LoginPayload payload = new LoginPayload() { LoginId = id, Password = password};
var request = NewRequest(HttpMethod.Post, "login");
JsonPayload<LoginPayload>(payload, ref request);
return await Execute<Account>(request, false);
}
// 204: success, anything else failure
internal async Task<Boolean> LogOut ()
{
return await Execute<Boolean>(NewRequest(HttpMethod.Delete, "login"), true);
}
internal async Task<HttpResponseMessage> GetRawResponse ()
{
return await Execute<HttpResponseMessage>(NewRequest(HttpMethod.Get, "raw/something"), true);
}
internal async Task<Int32> GetMeStatusCode ()
{
return await Execute<Int32>(NewRequest(HttpMethod.Get, "some/intstatus"), true);
}
private async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate)
{
if (authenticate)
AuthenticateRequest(ref request); // add auth token to request
var tcs = new TaskCompletionSource<RESULT>();
var response = await client.SendAsync(request);
// TODO: If the RESULT is just HTTPResponseMessage, the rest is unnecessary
if (response.IsSuccessStatusCode)
{
try
{
// TryParse needs to handle Boolean differently than other types
RESULT result = await TryParse<RESULT>(response);
tcs.SetResult(result);
}
catch (Exception e)
{
tcs.SetException(e);
}
}
else
{
try
{
APIError error = await TryParse<APIError>(response);
tcs.SetException(new APIException(error));
}
catch (Exception e)
{
tcs.SetException(new APIException("Unknown error"));
}
}
return tcs.Task.Result;
}
This is the APIError JSON structure (it's the status code + a custom error code).
{
"status": 404,
"code":216,
"msg":"User not found"
}
I would prefer to stay with System.Net, but that's mostly because I don't want to switch all my code over. If what I want is easier done in other ways then it's obviously worth the extra work.
Thanks.
Here is an example of how I've done it using MVC API 2 as backend. My backend returns a json result if the credentials are correct. UserCredentials class is the exact same model as the json result. You will have to use System.Net.Http.Formatting which can be found in the Microsoft.AspNet.WebApi.Client NugetPackage
public static async Task<UserCredentials> Login(string username, string password)
{
string baseAddress = "127.0.0.1/";
HttpClient client = new HttpClient();
var authorizationHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes("xyz:secretKey"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authorizationHeader);
var form = new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "username", username },
{ "password", password },
};
var Response = await client.PostAsync(baseAddress + "oauth/token", new FormUrlEncodedContent(form));
if (Response.StatusCode == HttpStatusCode.OK)
{
return await Response.Content.ReadAsAsync<UserCredentials>(new[] { new JsonMediaTypeFormatter() });
}
else
{
return null;
}
}
and you also need Newtonsoft.Json package.
public class UserCredentials
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
//more properties...
}
i would use a Deserializer.
HttpResponseMessage response = await client.GetAsync("your http here");
var responseString = await response.Content.ReadAsStringAsync();
[Your Class] object= JsonConvert.DeserializeObject<[Your Class]>(responseString.Body.ToString());
So, first to address the you need Newtonsoft.Json comments, I really haven't felt the need yet. I've found the built in support to work well so far (using the APIError Json in my original question:
[DataContract]
internal class APIError
{
[DataMember (Name = "status")]
public int StatusCode { get; set; }
[DataMember (Name = "code")]
public int ErrorCode { get; set; }
}
I have also defined a JsonHelper class to (de)serialize:
public class JsonHelper
{
public static T fromJson<T> (string json)
{
var bytes = Encoding.Unicode.GetBytes (json);
using (MemoryStream mst = new MemoryStream(bytes))
{
var serializer = new DataContractJsonSerializer (typeof (T));
return (T)serializer.ReadObject (mst);
}
}
public static string toJson (object instance)
{
using (MemoryStream mst = new MemoryStream())
{
var serializer = new DataContractJsonSerializer (instance.GetType());
serializer.WriteObject (mst, instance);
mst.Position = 0;
using (StreamReader r = new StreamReader(mst))
{
return r.ReadToEnd();
}
}
}
}
The above bits I already had working. As for a single method that would handle each request execution based on the type of result expected while it makes it easier to change how I handle things (like errors, etc), it also adds to the complexity and thus readability of my code. I ended up creating separate methods (all variants of the Execute method in the original question:
// execute and return response.StatusCode
private static async Task<HttpStatusCode> ExecuteForStatusCode (HttpRequestMessage request, bool authenticate = true)
// execute and return response without processing
private static async Task<HttpResponseMessage> ExecuteForRawResponse(HttpRequestMessage request, bool authenticate = true)
// execute and return response.IsSuccessStatusCode
private static async Task<Boolean> ExecuteForBoolean (HttpRequestMessage request, bool authenticate = true)
// execute and extract JSON payload from response content and convert to RESULT
private static async Task<RESULT> Execute<RESULT>(HttpRequestMessage request, bool authenticate = true)
I can move the unauthorized responses (which my current code isn't handling right now anyway) into a new method CheckResponse that will (for example) log the user out if a 401 is received.

Categories

Resources