HttpClient.SendAsync doesn't use DelegatingHandler when testing - c#

We have a few classes in our C# project that make calls out to 3rd party APIs. We're using HttpClient objects for the calls. We've set up our classes where we do these calls to accept an HttpClient so that when testing, we can use a custom/fake DelegatingHandler with the client.
We've set up our classes like this:
public class CallingService : ApiService
{
private readonly ISomeOtherService _someOtherService;
public CallingService (ILogger logger,
IConfigurationManager configurationManager,
ISomeOtherService someOtherService) : base(logger, configurationManager)
{
_someOtherService = someOtherService;
}
public CallingService (ILogger logger,
HttpClient client,
IConfigurationManager configurationManager,
ISomeOtherService someOtherService) : base(logger, configurationManager, client)
{
_someOtherService = someOtherService;
}
private async Task<XmlNodeList> TransmitToApi(string xml_string)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
//..
string type = "application/xml";
var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string)));
var targetUri = new Uri(ConfigurationManager.GetAppSetting("ApiUrl"));
var message = new HttpRequestMessage
{
RequestUri = targetUri ,
Method = HttpMethod.Post,
Content = content
};
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
message.Content.Headers.Add("Content-Type", type);
message.Headers.Add("someHeader", someData);
HttpResponseMessage response = null;
try
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
response = await Client.SendAsync(message, token);
}
catch (Exception ex)
{
throw ex;
}
//...
return someData;
}
The base ApiService class defines a generic HttpClient object if one is not provided.
We're currently using SendAsync so we can define the message headers. (We have more headers than are listed here.)
The test defines the DelegatingHandler like this:
public class FakeResponseHandler : DelegatingHandler
{
private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();
public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false)
{
if (!string.IsNullOrWhiteSpace(content))
{
if (asXml)
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml");
}
else
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
}
}
_fakeResponses.Add(uri, responseMessage);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_fakeResponses.ContainsKey(request.RequestUri))
{
return _fakeResponses[request.RequestUri];
}
return new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request };
}
}
And then:
[Fact]
public async Task ItWillDoStuffAndCallApi()
{
using (var mock = AutoMock.GetLoose())
{
mock.Mock<IConfigurationManager>()
.Setup(cm => cm.GetAppSetting("ApiUrl"))
.Returns("http://example.org/test/");
string testReturnData = GetFileContents("IntegrationTests.SampleData.SampleApiResponseXML.txt");
FakeResponseHandler fakeResponseHandler = new FakeResponseHandler();
fakeResponseHandler.AddFakeResponse(new Uri("http://example.org/test/"),
new HttpResponseMessage(HttpStatusCode.OK),
testReturnData,
true);
//HttpClient httpClient = new HttpClient(fakeResponseHandler);
HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler);
mock.Provide(httpClient);
var ourService = new CallingService();
ourService.TransmitToApi(someXmlString);
}
}
When we run the test, we receive the message:
Handler did not return a response message.
And we never seem to get into DelegatingHandler.SendAsync method.
We have other classes calling APIs using HttpClient.PostAsync or GetAsync, and these do call the DelegatingHandler.SendAsync method and work as expected.
We've tried:
HttpClient httpClient = new HttpClient(fakeResponseHandler);
and
HttpClient httpClient = HttpClientFactory.Create(fakeResponseHandler);
We've also tried Client.SendAsync with and without the cancellation token.
Why is this not working?
Should we re-write this to use PostAsync?

I'd need to see the implementation of HttpClientFactory.Create and what Client.SendAsync actually does internally but nevertheless I was able to use the sample code you provide and fill in the blanks where I could to get the following to work:
public class FakeResponseHandler : DelegatingHandler
{
private readonly Dictionary<Uri, HttpResponseMessage> _fakeResponses = new Dictionary<Uri, HttpResponseMessage>();
public void AddFakeResponse(Uri uri, HttpResponseMessage responseMessage, string content = "", bool asXml = false)
{
if (!string.IsNullOrWhiteSpace(content))
{
if (asXml)
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/xml");
}
else
{
responseMessage.Content = new StringContent(content, Encoding.UTF8, "application/json");
}
}
_fakeResponses.Add(uri, responseMessage);
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var emptyContent = string.Empty;
if (request.Content.Headers.ContentType.MediaType == "application/xml")
emptyContent = "<empty />";
return Task.FromResult(_fakeResponses.ContainsKey(request.RequestUri) ?
_fakeResponses[request.RequestUri] :
new HttpResponseMessage(HttpStatusCode.NotFound)
{
RequestMessage = request,
Content = new StringContent(emptyContent)
});
}
}
Just to make things clean use Task.FromResult to return a task in SendAsync and also provide an empty content to avoid null reference exceptions.
public class CallingService
{
private readonly HttpClient _httpClient;
private readonly IConfigurationManager _configurationManager;
public CallingService(HttpClient httpClient,
IConfigurationManager configurationManager)
{
_httpClient = httpClient;
_configurationManager = configurationManager;
}
public async Task<XmlNodeList> TransmitToApi(string xml_string)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
//..
string type = "application/xml";
var content = new StreamContent(new MemoryStream(Encoding.ASCII.GetBytes(xml_string)));
var targetUri = new Uri(_configurationManager.GetAppSetting("ApiUrl"));
var message = new HttpRequestMessage
{
RequestUri = targetUri,
Method = HttpMethod.Post,
Content = content
};
message.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
message.Content.Headers.Add("Content-Type", type);
string somedata;
try
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
var response = await _httpClient.SendAsync(message, token);
somedata = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
throw ex;
}
//...
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(somedata);
return xmlDoc.SelectNodes("*");
}
}
And then the test passes the instance of HttpClient to CallingService:
[TestMethod]
public async Task TestMethod1()
{
const string content = #"<root><test>1243</test></root>";
const string httpExample = "http://example.org/test/";
var configurationManager = new Mock<IConfigurationManager>();
configurationManager
.Setup(cm => cm.GetAppSetting("ApiUrl"))
.Returns(httpExample);
var fakeResponseHandler = new FakeResponseHandler();
fakeResponseHandler.AddFakeResponse(new Uri(httpExample),
new HttpResponseMessage(HttpStatusCode.OK), content, true);
using (var httpClient = new HttpClient(fakeResponseHandler))
{
var ourService = new CallingService(httpClient, configurationManager.Object);
var result = await ourService.TransmitToApi(content);
Assert.AreEqual(content, result.Item(0)?.OuterXml);
}
}
This all works so if I had to guess - the issue would be somewhere in your HttpClientFacotry.
Hope that helps!! Cheers, :)

Related

.Net Core - Docker Linux memory usage keeps increasing

I am bit frustrated now what's wrong with my code, and I hope you guys can help me with it, so here are the things I have tried.
so I tried making the HttpClient static, and I tried using the IHttpClientFactory.CreateClient() and I even added this on my .csproj
<ServerGarbageCollection>false</ServerGarbageCollection>
Here is the sample code that I have been doing
public class TestController : BaseController
{
private static HttpClient _httpClient = new();
public TestController()
{
}
[HttpGet("bills")]
public async Task<IActionResult> GetBillsPresentment([FromQuery] GetBillPresentmentQuery query)
{
if (!query.AccountNumber.Contains("-"))
query.AccountNumber = FormatAccountNumber(query.AccountNumber);
var billDetails = await GetBillDetail(query.AccountNumber);
if (billDetails == null)
throw new ProviderProcessException(ProviderErrorCode.INVALID_ACCOUNT_NUMBER);
return Ok(new BillPresentmentVm
{
User = new CustomerDto
{
CustomerName = billDetails.Name
},
Billing = new BillingDto
{
AccountNumber = query.AccountNumber,
DueDate = DateTime.Parse(billDetails.LastReadDate).AddMonths(1),
Outstanding = !string.IsNullOrEmpty(billDetails.Arrears) ? decimal.Parse(billDetails.Arrears) : null
}
});
}
private async Task<ResponseModel> GetBillDetail(string accountNumber)
{
try
{
var payload = new { accno = accountNumber };
string json = JsonConvert.SerializeObject(payload);
var buffer = System.Text.Encoding.UTF8.GetBytes(json);
using var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.PostAsync("https://test.com", byteContent);
if (!response.IsSuccessStatusCode)
throw new ProviderProcessException(ProviderErrorCode.BILLING_CYCLE_UNAVAILABLE);
var result = await response.Content.ReadAsStringAsync();
if (result == "Accno not found!") return null;
var data = JsonConvert.DeserializeObject<ResponseModel>(result);
return data;
}
catch (Exception)
{
throw new ProviderProcessException(ProviderErrorCode.BILLING_CYCLE_UNAVAILABLE);
}
}
private static string FormatAccountNumber(string accountNumber)
{
return string.Format("{0:#######-########}", Convert.ToInt64(accountNumber));
}
}
And here's the docker memory usage
The memory usage keeps increasing after a request. Can someone explains me why it is not decreasing?
Thank you very much in advance
I solve this issue using IHttpClientFactory instead of HttpClient.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
I use some thing like this and it works fine on large amount of requests per second and it use memory in normal way .
namespace BehsaLoyalty.ApiClient
{
public class ApiRepo : IApiRepo
{
private readonly IHttpClientFactory _HttpClientFactory;
public ApiRepo (IHttpClientFactory httpClientFactory)
{
_HttpClientFactory = httpClientFactory;
}
public async Task<ResponseModel> PostMyObject(Myobject model, CancellationToken cancellationToken)
{
HttpClient httpClient = _HttpClientFactory.CreateClient("ApiDestinationURI");
using HttpRequestMessage request = new(HttpMethod.Post, "/blah/blah");
request.Content = new StringContent(JsonSerializer.Serialize(model));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken);
response.EnsureSuccessStatusCode();
string createdContent = await response.Content.ReadAsStringAsync();
ResponseModel ResponseReturn = JsonSerializer.Deserialize<ResponseModel>(createdContent);
return ResponseReturn;
}
}
}

Adding a default header only for POST request

I'm trying a Typed HttpClient as below. I'm looking to find a way to add DefaultRequestHeaders only to my POST request (and not to other requests such GET or PUT). Is there way to achieve this?
Here is my code snippet.
var builder = services
.AddHttpClient("MyService", client =>
{
client.BaseAddress = configuration.BaseAddress;
// Need to default header only for "POST" request
client.DefaultRequestHeaders.Add("MyHeader", "MyHeaderValue");
})
.AddTypedClient<IMyServiceClient, MyServiceRestClient>();
I'm trying to find a way where line client.DefaultRequestHeaders.Add("MyHeader", "MyHeaderValue") is only effective for POST request.
What about a re-usable httpclient that has minimal config and then manage your request by http method specific execution?
For example, consider an async POST method that overrides an internal generic async method that configs the request/response and uses your httpclient to execute. You could pass in the headers you need and/or set the default headers in this method.
public async Task<KeyValuePair<HttpResponseMessage, T>> PostAsync<T>(Uri uri, object data, AuthenticationHeaderValue authHeader = null, Dictionary<string, string> headers = null)
{
return await SendRequestAsync<T, object>(uri.ToString(), data, HttpMethod.Post, authHeader, headers);
}
The internal method is as follows:
private async Task<KeyValuePair<HttpResponseMessage, T>> SendRequestAsync<T, U>(string requestUri, U content, HttpMethod method, AuthenticationHeaderValue authHeader = null, Dictionary<string, string> headers = null)
{
using (HttpRequestMessage request = new HttpRequestMessage())
{
request.Method = method;
request.RequestUri = new Uri(requestUri, UriKind.Absolute);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (authHeader != null)
{
request.Headers.Authorization = authHeader;
}
string requestContent = null;
if (content != null)
{
requestContent = JsonConvert.SerializeObject(content);
request.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
}
if (headers != null)
{
foreach (var header in headers)
{
if (!request.Headers.Contains(header.Key))
{
request.Headers.Add(header.Key, header.Value);
}
}
}
// _client would be a private implementation or injected version of your httpclient
using (HttpResponseMessage response = await _client.SendAsync(request))
{
if (response.IsSuccessStatusCode)
{
if (response.Content != null)
{
var rawJson = await response.Content.ReadAsStringAsync();
var mappedObj = JsonConvert.DeserializeObject<T>(rawJson);
var result = new KeyValuePair<HttpResponseMessage, T>(response, mappedObj);
return result;
}
}
else
{
// do something else
}
return new KeyValuePair<HttpResponseMessage, T>(response, default(T));
}
}
}
I was actually being stupid here. I realized that I could achieve what I wanted through the DelegatingHandler.
var builder = services
.AddHttpClient("MyService", client =>
{
client.BaseAddress = configuration.BaseAddress;
})
.AddHttpMessageHandler<MySpecialHeaderDelegatingHandler>()
.AddTypedClient<IMyServiceClient, MyServiceRestClient>();
public class MySpecialHeaderDelegatingHandler: DelegatingHandler
{
private const string MySpecialHeader = "my-special-header";
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
EnsureMySpecialHeaderExists(request);
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
private static void EnsureMySpecialHeaderExists(HttpRequestMessage request)
{
if (request.Method != HttpMethod.Post) return;
if (!request.Headers.Contains(MySpecialHeader))
{
request.Headers.Add(MySpecialHeader, "MyHeaderValue");
}
}

Refactoring code to allow for unit testing of HttpClient

I am dealing with a piece of code that looks like this:
public class Uploader : IUploader
{
public Uploader()
{
// assign member variables to dependency injected interface implementations
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler);
result = await client.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
I am trying to unit test the Upload function. In particular, I need to mock the HttpClient. After reading the other answers on here and these two articles, I know that one of the better ways to solve this is to mock the HttpMessageHandler instead and pass that to HttpClient and have it return whatever I want.
So, I started along that path by first passing in HttpClient in the constructor as a dependency:
public class Uploader : IUploader
{
private readonly HttpClient m_httpClient; // made this a member variable
public Uploader(HttpClient httpClient) // dependency inject this
{
m_httpClient = httpClient;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
result = await m_httpClient.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
and adding: services.AddSingleton<HttpClient>(); to the ConfigureServices method of Startup.cs.
But now I face a slight issue where the original code specifically creates a HttpClientHandler to pass in. How then do I refactor that to take in a mockable handler?
I find the simplest way is to continue using HttpClient, but pass in a mocking HttpClientHandler such as https://github.com/richardszalay/mockhttp
Code sample from the link above:
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}");
// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();
var response = await client.GetAsync("http://localhost/api/user/1234");
var json = await response.Content.ReadAsStringAsync();
Console.Write(json); // {'name' : 'Test McGee'}
The Dependency Injection framework built into .NET Core ignores internal constructors, so it will call the parameter-less constructor in this scenario.
public sealed class Uploader : IUploader
{
private readonly HttpClient m_httpClient;
public Uploader() : this(new HttpClientHandler())
{
}
internal Uploader(HttpClientHandler handler)
{
m_httpClient = new HttpClient(handler);
}
// regular methods
}
In your unit tests, you can use the constructor accepting the HttpClientHandler:
[Fact]
public async Task ShouldDoSomethingAsync()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://myserver.com/upload")
.Respond("application/json", "{'status' : 'Success'}");
var uploader = new Uploader(mockHttp);
var result = await uploader.UploadAsync();
Assert.Equal("Success", result.Status);
}
Normally I'm not a big fan of having an internal constructor to facilitate testing, however, I find this more obvious and self-contained than registering a shared HttpClient.
HttpClientFactory might be another good option, but I haven't played around with that too much, so I'll just give info on what I've found useful myself.
One way would be to abstract your HTTP functionality into a service i.e. HttpService which implements an interface of IHttpService:
IHttpService
public interface IHttpService
{
Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null);
}
HttpService
public class HttpService : IHttpService
{
private static HttpClient _httpClient;
private const string MimeTypeApplicationJson = "application/json";
public HttpService()
{
_httpClient = new HttpClient();
}
private static async Task<HttpResponseMessage> HttpSendAsync(HttpMethod method, Uri url, string payload,
Dictionary<string, string> headers = null)
{
var request = new HttpRequestMessage(method, url);
request.Headers.Add("Accept", MimeTypeApplicationJson);
if (headers != null)
{
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
if (!string.IsNullOrWhiteSpace(payload))
request.Content = new StringContent(payload, Encoding.UTF8, MimeTypeApplicationJson);
return await _httpClient.SendAsync(request);
}
public async Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null)
{
return await HttpSendAsync(HttpMethod.Post, url, payload, headers);
}
}
Add to your services:
services.AddSingleton<IHttpService, HttpService>();
In your class you would then inject IHttpService as a dependency:
public class Uploader : IUploader
{
private readonly IHttpService _httpService; // made this a member variable
public Uploader(IHttpService httpService) // dependency inject this
{
_httpService = httpService;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
result = await _httpService.PostAsync(new Uri(url), data);
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
You could then use Moq to mock HttpService in your unit test:
[TestClass]
public class UploaderTests
{
private Mock<IHttpService> _mockHttpService = new Mock<IHttpService>();
[TestMethod]
public async Task WhenStatusCodeIsNot200Ok_ThenErrorMessageReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(url, data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));
// act
var result = await uploader.Upload(new Uri(url), data);
// assert
Assert.AreEqual("Some Error Message", result);
}
[TestMethod]
public async Task WhenStatusCodeIs200Ok_ThenNullReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(new Uri(url), data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
// act
var result = await uploader.Upload(url, data);
// assert
Assert.AreEqual(null, result);
}
}

Calling web service asynchronously in page constructor

I need to load data on a XAML page in a windows 10 UWP application. For that I wrote code to call the web service in async task function, and I call this in page constructor. Could you please tell best way to do this? Following is my code.
public sealed partial class MyDownloads : Page
{
string result;
public MyDownloads()
{
this.InitializeComponent();
GetDownloads().Wait();
string jsonstring = result;
//code for binding follows
}
private async Task GetDownloads()
{
JsonObject jsonObject = new JsonObject
{
{"StudentID", JsonValue.CreateStringValue(user.Student_Id.ToString()) },
};
string ServiceURI = "http://m.xxx.com/xxxx.svc/GetDownloadedNotes";
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, ServiceURI);
request.Content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json");
HttpResponseMessage response = await httpClient.SendAsync(request);
string returnString = await response.Content.ReadAsStringAsync();
result = returnString;
}
}
Instead that you need use OnNavigatedTo
because, GetDownloads().Wait() bad practice. You block UI Thread until the end of execution
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var result = await GetDownloadsAsync();
string jsonstring = result;
}
private async Task<string> GetDownloadsAsync()
{
JsonObject jsonObject = new JsonObject
{
{"StudentID", JsonValue.CreateStringValue(user.Student_Id.ToString()) },
};
string ServiceURI = "http://m.xxx.com/xxxx.svc/GetDownloadedNotes";
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, ServiceURI);
request.Content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json");
HttpResponseMessage response = await httpClient.SendAsync(request);
string returnString = await response.Content.ReadAsStringAsync();
return returnString;
}
}

How should I implement ExecuteAsync with RestSharp on Windows Phone 7?

I'm attempting to use the documentation on the RestSharp GitHub wiki to implement calls to my REST API service but I'm having an issue with the ExecuteAsync method in particular.
Currently my code looks like this for the API class:
public class HarooApi
{
const string BaseUrl = "https://domain.here";
readonly string _accountSid;
readonly string _secretKey;
public HarooApi(string accountSid, string secretKey)
{
_accountSid = accountSid;
_secretKey = secretKey;
}
public T Execute<T>(RestRequest request) where T : new()
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment);
client.ExecuteAsync<T>(request, (response) =>
{
return response.Data;
});
}
}
I'm aware this slightly deviates from what is on the GitHub page but I'm using this with WP7 and believe the example is for C# hence the usage of the ExecuteAsync method.
My problem is with what the ExecuteAsync command should contain. I can't use return response.Data because I'm warned:
'System.Action<RestSharp.RestResponse<T>,RestSharp.RestRequestAsyncHandle>' returns void, a return keyword must not be followed by an object expression
Does anyone have any insight on how to fix this or a tutorial that may assist?
Old question but if you are using C# 5 you can have a generic execute class by creating a TaskCompleteSource that resturns a Task of T. Your code could look like this:
public Task<T> ExecuteAsync<T>(RestRequest request) where T : new()
{
var client = new RestClient();
var taskCompletionSource = new TaskCompletionSource<T>();
client.BaseUrl = BaseUrl;
client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment);
client.ExecuteAsync<T>(request, (response) => taskCompletionSource.SetResult(response.Data));
return taskCompletionSource.Task;
}
And use it like this:
private async Task DoWork()
{
var api = new HarooApi("MyAcoountId", "MySecret");
var request = new RestRequest();
var myClass = await api.ExecuteAsync<MyClass>(request);
// Do something with myClass
}
As an alternative (or complement) to the fine answer by Gusten. You can use ExecuteAsync. This way you do not manually have to handle TaskCompletionSource. Note the async keyword in the signature.
Update:
As of 106.4.0 ExecuteTaskAsync is obsolete. Since 104.2 you should use ExecuteAsync instead:
public async Task<T> ExecuteAsync<T>(RestRequest request) where T : new()
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment);
IRestResponse<T> response = await client.ExecuteAsync<T>(request);
return response.Data;
}
Old Answer:
public async Task<T> ExecuteAsync<T>(RestRequest request) where T : new()
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment);
IRestResponse<T> response = await client.ExecuteTaskAsync<T>(request); // Now obsolete
return response.Data;
}
Your code should look something like this:
public class HarooApi
{
const string BaseUrl = "https://domain.here";
readonly string _accountSid;
readonly string _secretKey;
public HarooApi(string accountSid, string secretKey)
{
_accountSid = accountSid;
_secretKey = secretKey;
}
public void ExecuteAndGetContent(RestRequest request, Action<string> callback)
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment);
client.ExecuteAsync(request, response =>
{
callback(response.Content);
});
}
public void ExecuteAndGetMyClass(RestRequest request, Action<MyClass> callback)
{
var client = new RestClient();
client.BaseUrl = BaseUrl;
client.Authenticator = new HttpBasicAuthenticator(_accountSid, _secretKey);
request.AddParameter("AccountSid", _accountSid, ParameterType.UrlSegment);
client.ExecuteAsync<MyClass>(request, (response) =>
{
callback(response.Data);
});
}
}
I added two methods, so you can check what you want (string content from the response body, or a deserialized class represented here by MyClass)
Or more precisely like this:
public async Task<IRestResponse<T>> ExecuteAsync<T>(IRestRequest request) where T : class, new()
{
var client = new RestClient(_settingsViewModel.BaseUrl);
var taskCompletionSource = new TaskCompletionSource<IRestResponse<T>>();
client.ExecuteAsync<T>(request, restResponse =>
{
if (restResponse.ErrorException != null)
{
const string message = "Error retrieving response.";
throw new ApplicationException(message, restResponse.ErrorException);
}
taskCompletionSource.SetResult(restResponse);
});
return await taskCompletionSource.Task;
}
The following did the job
public async Task<IRestResponse<T>> ExecuteAsync<T>(IRestRequest request) where T : class, new()
{
var client = new RestClient
{
BaseUrl = _baseUrl,
Authenticator = new HttpBasicAuthenticator(_useraname, _password),
Timeout = 3000,
};
var tcs = new TaskCompletionSource<T>();
client.ExecuteAsync<T>(request, restResponse =>
{
if (restResponse.ErrorException != null)
{
const string message = "Error retrieving response.";
throw new ApplicationException(message, restResponse.ErrorException);
}
tcs.SetResult(restResponse.Data);
});
return await tcs.Task as IRestResponse<T>;
}
Since public static RestRequestAsyncHandle ExecuteAsync(this IRestClient client, IRestRequest request, Action<IRestResponse> callback) has been deprecated, you should look to using public Task<IRestResponse> ExecuteAsync(IRestRequest request, CancellationToken token = default) instead.
The following code
client.ExecuteAsync(request, response => { callback(response.Content); });
Should instead become
await client.ExecuteAsync(request).ContinueWith(task => callback(task.Result.Content));

Categories

Resources