I think I might be retarded or I'm asking too much out of C# but I can't get this to work.
What essentially I'm trying to do is to wrap an API-client with some logging functions and a method to request a new token from the API-server.
controller:
public class TestController : ApiController
{
[HttpGet]
[Route("api/model/get/{id}"]
public IHttpActionResult GetModel<Model>(int id)
{
var result = Service.DoHttp<Model>(ServiceClass.GetModel, id);
}
}
service:
public static class ServiceClass
{
private static readonly HttpClient client = new HttpClient() { BaseAddress = new Uri(Globals.ExternalApiPath) };
private static string TokenHeader = "";
public async static Task<HttpResponseMessage> GetModel(int id)
{
var response = client.GetAsync($"/api/get/{id}");
return await response;
}
public static T DoHttp<T>(Func<int, HttpResponseMessage> funk, int id)
{
try
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", TokenHeader);
var result = funk(id);
if (result.IsSuccessStatusCode)
{
return result.Content.ReadAsAsync<T>().Result;
}
else
{
throw new Exception(String.Format($"Unknown error! Unable to contact remote API! AccessToken: {TokenHeader} Status code: {result.StatusCode}"));
}
}
catch (Exception e)
{
// log ex
throw e;
}
}
}
But my Service.DoHttp(Service.GetModel, id); complains about it being the wrong return type.
What am I doing wrong or have I misunderstood the whole concept?
EDIT: Compiler complains about 'Task ServiceClass.GetModel(int)' has the wrong return type
Change the DoHttp method to the following.
public static T DoHttp<T>(Func<int, Task<HttpResponseMessage>> funk, int id)
As the GetModel method returns a Task you need to use a task as the return type of the Func too.
Related
The client and a generic method for the API requests are created here:
public class Client : IDisposable
{
private HttpClient _client;
private void CreateClient()
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Accept.Clear();
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
_client.DefaultRequestHeaders.Add("KEY", token);
}
public void Dispose() => _client?.Dispose();
public enum Method { GET, POST, PUT, DELETE }
public HttpResponseMessage Request(string url, Method method, object data, HttpContent request)
{
if (data != null)
request = new StringContent(Serialize(data), Encoding.UTF8, "application/json");
switch (method)
{
case Method.POST: return _client.PostAsync(url, request).Result;
case Method.PUT: return _client.PutAsync(url, request).Result;
case Method.DELETE: return _client.DeleteAsync(url).Result;
default: return _client.GetAsync(url).Result;
}
}
public Task<HttpResponseMessage> RequestAsync(string url, Method method, object data, HttpContent request)
{
if (data != null)
request = new StringContent(Serialize(data), Encoding.UTF8, "application/json");
switch (method)
{
case Method.GET: return _client.GetAsync(url);
case Method.POST: return _client.PostAsync(url, request);
case Method.PUT: return _client.PutAsync(url, request);
case Method.DELETE: return _client.DeleteAsync(url);
default: return _client.GetAsync(url);
}
}
public string Post(string url, object data) =>
Request(url, Method.POST, data, null).Content.ReadAsStringAsync().Result;
public Task<HttpResponseMessage> PostAsync(string url, object data) =>
RequestAsync(url, Method.POST, data, null);
//UTILS
private static string Serialize(object data) =>
data == null
? string.Empty
: JsonConvert.SerializeObject(data, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}
I'm trying to call these methods to specific classes, to simplify the usage of it for the customer. For example, to create a new checkout for a transaction in a credit card:
public class Checkout : SDK
{
private static Client client;
public Checkout() => client = new Client();
public static async Task Credit(object data) =>
await client.PostAsync(url, data);
}
The request needs to be mounted based on a few models, that can have this structure and I'm trying to generate it in a simple way, like this:
public async Task Test()
{
var transaction = new Transaction
{
PaymentMethod = new PaymentMethod { Code = "1" },
Application = "Test",
Vendor = "Name",
Customer = new Customer
{
//Customer details...
},
Products = new List<TransactionProduct>
{
//Products...
}
};
var teste = Checkout.Credit(transaction);
Console.WriteLine(teste);
}
And all I get as return is:
System.Threading.Tasks.Task`1[System.Threading.Tasks.VoidTaskResult]
Id = 1, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"
I've tried to add await for the Checkout.Credit call, but I get:
CS0815 Test C# Cannot assign void to an implicitly-typed variable
Unit testing this with a simple HttpClient requests works like a charm, but I'm not being able to identify the problem on my project structure, so any help will be very much appreciated.
Task is the return type for an async method that does not have a return value.
Or, to put it another way, async wraps T values into Task<T> (or void returns into Task), and await unwraps those values. Since Credit returns Task, the type of the expression Checkout.Credit(transaction) is Task, and the type of the expression await Checkout.Credit(transaction) is void. And you cannot assign void to var teste; that's what the compiler error is saying.
To fix this, give your async method return types. In particular:
public static async Task<HttpResponseMessage> Credit(object data) =>
await client.PostAsync(url, data);
On a side note, this is quite strange:
public string Post(string url, object data) => ...;
public Task<HttpResponseMessage> PostAsync(string url, object data) => ...;
Usually, if you have a Method and a MethodAsync where Method has some return type TResult, then MethodAsync will have the return type Task<TResult>, not Task<SomeCompletelyDifferentType>. A more natural API would be something like this:
public async Task<HttpResponseMessage> PostAsync(string url, object data)
{
var result = await Request(url, Method.POST, data, null);
return await result.Content.ReadAsStringAsync();
}
I want to try and refactor this method a bit better while I am having some difficulty in understand the async I have a web api2 project which I use my data access layer to share to a phone app. I am not sure if I have the syntax correct I am using a xamrian shared library here in a xamrian forms app.
I will have various methods link get clients which will have the end point API/clients but obv their return type would be different.
How would one make the below work well with a list view say.
How would I consume the below method as well , what is general practise to then store the jobs locally in sql lite.
public string BaseUrl { get; set; }
public string EndPoint { get; set; }
Lets go out to the web service and grab the job list.
public async List<Job> GetJSON()
{
List<Job> rootObject = new List<Job>();
try
{
var client = new System.Net.Http.HttpClient();
var response = await client.GetAsync("http://myinternaliis/api/job");
string json = await response.Content.ReadAsStringAsync();
if (json != "")
{
rootObject = JsonConvert.DeserializeObject< List<Job>>(json);
}
}
catch (InvalidCastException e)
{
throw e;
}
return await rootObject;
}
Thanks for help in improving my understanding.
I guess you are looking for something like:
public async Task<T> GetJson<T>(string url)
{
using (var client = new System.Net.Http.HttpClient())
{
var response = await client.GetAsync(url);
var json = await response.Content.ReadAsStringAsync();
return (T)JsonConvert.DeserializeObject<T>(json);
}
}
Usually I have:
IApi - defines all the API methods
IHttpService - defines methods like Get, Post and etc.
IJsonConverter - defines methods like serialize and deserialize.
Here is an example:
public interface IJsonConverter
{
T Deserialize<T>(string json);
string Serialize<T>(T data);
}
public class JsonConveter : IJsonConverter
{
public T Deserialize<T>(string json) => JsonConvert.DeserializeObject<T>(json);
public string Serialize<T>(T data) => JsonConvert.Serialize(data);
}
public interface IHttpService
{
Task<T> Get<T>(string url);
}
public class HttpService : IHttpService
{
readonly IJsonConverter jsonConverter;
public HttpService(IJsonConverter jsonConverter)
{
this.jsonConverter = jsonConverter;
}
public async Task<T> Get<T>(string url)
{
using (var client = new System.Net.Http.HttpClient())
{
var response = await client.GetAsync(url);
var json = await response.Content.ReadAsStringAsync();
return jsonConverter.Deserialize<T>(json);
}
}
}
public interface IApi
{
Task<List<Job>> GetJobs();
}
public class Api : IApi
{
readonly string url = "http://myinternaliis/api/";
readonly IHttpService httpService;
public Api(IHttpService httpService)
{
this.httpService = httpService;
}
public Task<List<Job>> GetJobs() => httpService.Get<List<Job>>($"{url}job");
}
The app I'm working on is supposed to retrieve a json string with the http client after which it gets deserialised and used in the app.
Everything works, except for the await functionality. I'm doing something wrong and I can't seem to figure out what. How can I make sure that my DataService class waits untill I have my json and it has been deserialized?
The DataService class:
class DataService : IDataService
{
private IEnumerable<Concert> _concerts;
public DataService()
{
_concerts = new DataFromAPI()._concerts;
Debug.WriteLine("____Deserialization should be done before continuing____");
**other tasks that need the json**
}
}
My http client class:
class DataFromAPI
{
public IEnumerable<Concert> _concerts { get; set; }
public DataFromAPI()
{
Retrieve();
}
public async Task Retrieve()
{
try
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
var result = await client.GetAsync(new Uri("http://url-of-my-api"), HttpCompletionOption.ResponseContentRead);
string jsonstring = await result.Content.ReadAsStringAsync();
DownloadCompleted(jsonstring);
}
catch {}
}
void DownloadCompleted(string response)
{
try
{
_concerts = JsonConvert.DeserializeObject<IEnumerable<Concert>>(response.ToString());
}
catch {}
}
}
solution
After a lot of trial and error I realised that for this particular thingy it didn't have to be async, so I just recreated is on the main thread, with success:
The DataService class:
class DataService : IDataService
{
private IEnumerable<Concert> _concerts;
public DataService()
{
_concerts = new DataFromAPI()._concerts;
}
}
My http client class:
public static class DataFromAPI
{
public void Retrieve()
{
try
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
var result = client.GetAsync("http://url-of-my-api").Result;
if (result.IsSuccessStatusCode)
{
var responseContent = result.Content;
}
DownloadCompleted(result.Content.ReadAsStringAsync().Result);
}
catch {}
}
}
You are calling Retrieve() without await in the DataFromAPI constructor, That's why your method isn't awaited.
You should better call this methods outside the constructor, with the await keyword like this :
await Retrieve();
You have to refactor your code a little. Here's an example :
public class DataService : IDataService
{
private IEnumerable<Concert> _concerts;
public async Task LoadData()
{
_concerts = await DataFromAPI.Retrieve();
**other tasks that need the json**
}
}
public static class DataFromAPI
{
public static async Task<IEnumerable<Concert>> Retrieve()
{
try
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
var result = await client.GetAsync(new Uri("http://url-of-my-api"), HttpCompletionOption.ResponseContentRead);
string jsonstring = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<Concert>>(response.ToString());
}
catch(Exception)
{
}
return Enumerable.Empty<Concert>();
}
}
Then, when you create your DataService instance, just after you have to call it's LoadData() method.
DataService ds = new DataService();
await ds.LoadData();
And of course, these two lines of code must also be called from an async method. (async / await all the way)
After a lot of trial and error I realised that for this particular thingy it didn't have to be async, so I just recreated is on the main thread, with success:
The DataService class:
class DataService : IDataService
{
private IEnumerable<Concert> _concerts;
public DataService()
{
_concerts = new DataFromAPI()._concerts;
}
}
My http client class:
public static class DataFromAPI
{
public void Retrieve()
{
try
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage();
var result = client.GetAsync("http://url-of-my-api").Result;
if (result.IsSuccessStatusCode)
{
var responseContent = result.Content;
}
DownloadCompleted(result.Content.ReadAsStringAsync().Result);
}
catch {}
}
}
Is there a way I can use the new IHttpActionResult interface to return a HttpStatusCode.NoContent response message?
I am currently using return new HttpResponseMessage( HttpStatusCode.NoContent );
and would like to convert this into return NoContent();.
IHttpActionResult has already got Ok(), Conflict() and NotFound() but I cannot find any for Forbidden() and NoContent() which I need to use in my project.
How easy is it to add other result types?
There's no convenience method for no-content result because, by default, when a action returns void, the response will have the HTTP status 204.
If you wish to explicitly indicate that on the action, you could also return a StatusCode(HttpStatusCode.NoContent) from your action or a
ResponseMessage(new HttpResponseMessage(HttpStatusCode.NoContent)).
The Unauthorized() convenience method gives you a 401 status so, for Forbidden (403), you would also have to use StatusCode(HttpStatusCode.Forbidden) or
ResponseMessage(new HttpResponseMessage(HttpStatusCode.Forbidden))
I found this example site that shows how to add a custom IHttpActionResult method and I've used this to create the Forbidden() and NoContent() methods with great success.
public abstract class CommonApiController : ApiController
{
public class ForbiddenResult : IHttpActionResult
{
private readonly HttpRequestMessage _request;
private readonly string _reason;
public ForbiddenResult(HttpRequestMessage request,string reason)
{
_request = request;
_reason = reason;
}
public ForbiddenResult(HttpRequestMessage request)
{
_request = request;
_reason = "Forbidden";
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = _request.CreateResponse(HttpStatusCode.Forbidden,_reason);
return Task.FromResult(response);
}
}
public class NoContentResult : IHttpActionResult
{
private readonly HttpRequestMessage _request;
private readonly string _reason;
public NoContentResult(HttpRequestMessage request,string reason)
{
_request = request;
_reason = reason;
}
public NoContentResult(HttpRequestMessage request)
{
_request = request;
_reason = "No Content";
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = _request.CreateResponse(HttpStatusCode.NoContent,_reason);
return Task.FromResult(response);
}
}
}
And then I can use it like this:
public class InvoiceController : CommonApiController
{
public async Task<IHttpActionResult> Post([FromBody]Invoice invoice)
{
if(User.IsInRole("Readonly"))
{
return Forbidden();
}
// Rest of code
}
}
I tried the #Intrepid implementation and I ran into some problems. I see two solutions here:
Solution 1:
The part: return Forbidden(); should not work.
The compiler would not recognize this.
Instead it should be: return new ForbiddenResult(Request, "my reason");
UPDATE 1
Solution 2:
I think this is what #Interpid intended in his implementation, but he was missing a few things.
In order to use return Forbidden(); the CommonApiController should be updated with the functions that return the custom IHttpActionResult for Forbidden and NoContent
The class should look like this:
public abstract class CommonApiController: ApiController {
protected ForbiddenResult Forbidden() {
return new ForbiddenResult(this.Request);
}
protected ForbiddenResult Forbidden(string reason) {
return new ForbiddenResult(this.Request, reason);
}
protected NoContentResult NoContent() {
return new NoContentResult(this.Request);
}
public class ForbiddenResult: IHttpActionResult {
private readonly HttpRequestMessage _request;
private readonly string _reason;
public ForbiddenResult(HttpRequestMessage request, string reason) {
_request = request;
_reason = reason;
}
public ForbiddenResult(HttpRequestMessage request) {
_request = request;
_reason = "Forbidden";
}
public Task < HttpResponseMessage > ExecuteAsync(CancellationToken cancellationToken) {
var response = _request.CreateResponse(HttpStatusCode.Forbidden, _reason);
return Task.FromResult(response);
}
}
public class NoContentResult: IHttpActionResult {
private readonly HttpRequestMessage _request;
private readonly string _reason;
public NoContentResult(HttpRequestMessage request, string reason) {
_request = request;
_reason = reason;
}
public NoContentResult(HttpRequestMessage request) {
_request = request;
_reason = "No Content";
}
public Task < HttpResponseMessage > ExecuteAsync(CancellationToken cancellationToken) {
var response = _request.CreateResponse(HttpStatusCode.NoContent, _reason);
return Task.FromResult(response);
}
}
}
Anyway, if I am wrong and #Interpid's answer is correct. What am I missing here to make his implementation work?
You can now use the following (.Net Standard):
return StatusCode(HttpStatusCode.NoContent);
or (.Net Core 2.1+)
return NoContent();
If you want to include a reason phrase with your response without adding a sub-class to ApiController, build a ResponseMessage object and return it from the action by the ResponseMessage() method. Try this:
public class InvoiceController : ApiController
{
public async Task<IHttpActionResult> Post([FromBody]Invoice invoice)
{
if(User.IsInRole("Readonly"))
{
var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
response.ReasonPhrase = "User has the Readonly role";
return ResponseMessage(response);
}
// Rest of code
}
}
This worked well for me:
public class CodeAndReason : IHttpActionResult
{
private readonly HttpStatusCode code;
private readonly string reason;
public CodeAndReason(HttpStatusCode code, string reason)
{
this.code = code;
this.reason = reason;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(code)
{
ReasonPhrase = reason,
Content = new StringContent(reason),
};
return Task.FromResult(response);
}
public static IHttpActionResult NotFound(string reason)
{
return new CodeAndReason(HttpStatusCode.NotFound, reason);
}
public static IHttpActionResult Conflict(string reason)
{
return new CodeAndReason(HttpStatusCode.Conflict, reason);
}
public static IHttpActionResult Unauthorized(string reason)
{
return new CodeAndReason(HttpStatusCode.Unauthorized, reason);
}
}
Used as:
return CodeAndReason.NotFound("Record {blah} not found");
I have a url that contains valid xml, but unsure on how I could retrieve this with RestClient. I thought I could just download the string and then parse it like I allready to with WebClient.
Doing:
public static Task<String> GetLatestForecast(string url)
{
var client = new RestClient(url);
var request = new RestRequest();
return client.ExecuteTask<String>(request);
}
Makes VS cry about that 'string' must be a non-abstract type with a public parameterless constructor.
See executetask:
namespace RestSharp
{
public static class RestSharpEx
{
public static Task<T> ExecuteTask<T>(this RestClient client, RestRequest request)
where T : new()
{
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.AttachedToParent);
client.ExecuteAsync<T>(request, (handle, response) =>
{
if (response.Data != null)
tcs.TrySetResult(response.Data);
else
tcs.TrySetException(response.ErrorException);
});
return tcs.Task;
}
}
}
Thanks to Claus Jørgensen btw for a awesome tutorial on Live Tiles!
I just want to download the string as I already have a parser waiting for it to parse it :-)
If all you want is a string, just use this approach instead:
namespace RestSharp
{
public static class RestSharpEx
{
public static Task<string> ExecuteTask(this RestClient client, RestRequest request)
{
var tcs = new TaskCompletionSource<string>(TaskCreationOptions.AttachedToParent);
client.ExecuteAsync(request, response =>
{
if (response.ErrorException != null)
tcs.TrySetException(response.ErrorException);
else
tcs.TrySetResult(response.Content);
});
return tcs.Task;
}
}
}