HTTPClient for calling REST service is not working - c#

I am trying to consume REST service which returns me the data in JSON format. And I am using simple logic. But code get stuck on the line . I have check this code from emulator as well as from Device.
var response = await httpClient.SendAsync(request);
Here is my code
public async Task<string> GetMyData(string urlToCall)
{
using (HttpClient httpClient = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage(System.Net.Http.HttpMethod.Get, urlToCall);
var response = await httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
and calling is like,
string result = GetMyData(url).Result;
Is I am missing something? I want to get the data synchronously, instead of WebClient I choose HTTPClient for synchronous data call.
Can anyone please suggest me any solution.
EDIT
Thanks #Mike Richards for pointing me in right direction
I want to achieve the flow like below,
Call from Main.xaml.cs for aunticateuser() -> calling aunticateuser() methode in Process Class-> once response get from service return control back to Main.xaml.cs -> naviagate to another page i.e. Dashboard.xaml
But in real case its working like, call going to aunticateuser() methode in Process Class and where its send request to service and coming back to Main.xaml.cs but not waiting there to listen the response so how I will decide is this user is valid or not until I get the response?
Code
private void ApplicationBarDone_Click(object sender, EventArgs e)
{
bool isValid = checkForValidation();
if (isValid)
{
string url = txtUrl.Text.ToString().TrimStart(' ').TrimEnd(' ');
string login = txtLogin.Text.ToString().TrimStart(' ').TrimEnd(' ');
string clientKey = txtSystemKey.Text.ToString().TrimStart(' ').TrimEnd(' ');
string psw = txtPassword.Text.ToString().TrimStart(' ').TrimEnd(' ');
RestfulWebServiceUserDAO userDAO = new RestfulWebServiceUserDAO();
userDAO.authenticateUser(login, psw, clientKey, url);
//navigate to another page from here ??
}
}
In RestfulWebServiceUserDAO class authenticateUser() methode present
public async override void authenticateUser(string login, string password, string clientkey, string systemurl)
{
string url = systemurl + "/rest/user?login=" + login + "&password=" + password + "&clientkey=" + clientkey + "&languageId=" + 1;
string result = await GetMyData(url);
//or shall I navigate from here?? this is not allowed :|
}
GetMyData() methode same in RestfulWebServiceUserDAO class that what I have mark above.

Once you go async, you need to go async all the way to the top. But you need to always return an awaitable (commonly a Task).
Exception made for event handlers and method overrides. But in this case those will be fire-and-forget.
What's hapening in your code is that, although inside your authenticateUser (which, by convention, should be called AuthenticateUserAsync) method you are awaiting for the result of GetMyData (again, GetMyDataAsync), you are not returning any awaitable results to the caller. Becaus aysnc methods return to the caller as soon as they hit the first await statement.
Because of that, when ApplicationBarDone_Click calls userDAO.authenticateUser it imediately returns because it's not awaitable.
If you can't change authenticateUser, you simply can't use async-await. But being Windows Phone, network calls need to be asynchronous and you need to change that. Being so, your AuthenticateUserAsync method (remember the convention?) should look something like this:
public async override Task<string> AuthenticateUserAsync(string login, string password, string clientkey, string systemurl)
{
string url = systemurl + "/rest/user?login=" + login + "&password=" + password + "&clientkey=" + clientkey + "&languageId=" + 1;
return await GetMyDataAsync(url);
}
And your ApplicationBarDone_Click should look something like this:
private async void ApplicationBarDone_Click(object sender, EventArgs e)
{
bool isValid = checkForValidation();
if (isValid)
{
string url = txtUrl.Text.ToString().TrimStart(' ').TrimEnd(' ');
string login = txtLogin.Text.ToString().TrimStart(' ').TrimEnd(' ');
string clientKey = txtSystemKey.Text.ToString().TrimStart(' ').TrimEnd(' ');
string psw = txtPassword.Text.ToString().TrimStart(' ').TrimEnd(' ');
RestfulWebServiceUserDAO userDAO = new RestfulWebServiceUserDAO();
string result = await userDAO.AuthenticateUserAsync(login, psw, clientKey, url);
// do whatever you need to do after login.
// just remember that, during the call to userDAO.AuthenticateUserAsync
// the UI is responsive and the user can click again
}
}
To learn more about asyn-await programming, read my curation.

Try awaiting GetMyData
string result = await GetMyData(url)

Related

Can I add a list or array of query strings to uribuilder?

I'm using UriBuilder to create a url for an API endpoint.
I need to add some query strings for it, and I can do that nicely with the following example:
private async Task<string> CallAPI(string url, string queryString)
{
string s = "https://mysite.wendesk.com/api/v2/search/export/";
UriBuilder uriBuild = new UriBuilder(s);
uriBuild.Query = queryString;
using (var result = await _HttpClient.GetAsync($"{uriBuild.Uri.ToString()}"))
{
if (!result.IsSuccessStatusCode)
{
throw new Exception("bad status code from zendesk");
}
return await result.Content.ReadAsStringAsync();
}
}
Which is easy and nice. But I need to a quite a few query strings, and depending on who's calling the function, I need varying amounts.
So another solution could be something like this:
private async Task<string> CallAPI(string url, string[] queryStrings)
{
string s = "https://mysite.wendesk.com/api/v2/search/export/";
UriBuilder uriBuild = new UriBuilder(s);
uriBuild.Query = string.Join("&", queryStrings);
using (var result = await _HttpClient.GetAsync($"{uriBuild.Uri.ToString()}"))
{
if (!result.IsSuccessStatusCode)
{
throw new Exception("bad status code from zendesk");
}
return await result.Content.ReadAsStringAsync();
}
}
But I was wondering if there was something that could feel more "native". Perhaps something like creating a dicitionary with keys and values, so that the caller could just create a dictionary instead of hardcoding so much of the query strings in there?
I think NameValueCollection might work for a solution like you mentioned. You can use a dynamically method.
For example:
private Task<string> CreateQuery(NameValueCollection nvc)
{
var values = from key in nvc.AllKeys
from value in nvc.GetValues(key)
select string.Format(
"{0}={1}",
HttpUtility.UrlEncode(key),
HttpUtility.UrlEncode(value));
return Task.FromResult("?" + Join("&", values));
}

C# WebApi - Sending long text in HTTPRequest not working

I am working on a WebApp (Razor Pages) that work also as API Gateway. The WebApp get some data from another project (part of the same solution) that is a WebAPI.
The problem is that when I do an HTTPRequest to the WebAPI, if the request is not too long, the WebAPI will process it, but when I try to send a longer request (long in characters) it will reject it and send back a 404.
The WebApp is a basic CMS. So the app will provide to the user, the creation of Web pages. I am using a restful request model so a request will look like this:
string baseURL = #"https://localhost:5001";
public async Task<string> CreatePageAsync(string pageTitle, string pageBody, int? pageOrder, string userID)
{
if (pageTitle != null && pageBody != null && pageOrder != null && userID != null)
{
string fullURL = baseURL + $"/api/pages/create/page/title/{pageTitle}/body/{pageBody}/order/{pageOrder}/user/{userID}";
var request = new HttpRequestMessage(HttpMethod.Post, fullURL);
HttpResponseMessage response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
return "error";
}
}
return "ok";
}
As you can imagine, the "pageBody" property is the one responsible for the length of the request. So, when I test the WebAPI with short words, it works just fine, but if I copy an article from the internet (Just text) and use it as the body (simulating the user's content), if it is a long one, it will return a 404.
On the other end, the WebAPI looks like this:
[HttpPost("Create/page/title/{pageTitle}/body/{pageBody}/order/{pageOrder}/user/{userID}")]
//[ValidateAntiForgeryToken]
public async Task<string> CreatePage(string pageTitle, string pageBody, int pageOrder, string userID) //[Bind("pageName,pageHead,pageBody,userID")]
{
if (ModelState.IsValid)
{
DateTime now = DateTime.Now;
WebPage newPage = new WebPage()
{
PageID = _globalServices.GuidFromString(_globalServices.GetSeed()),
PageDateCreated = now,
PageDateUpdated = now,
PageOrder = pageOrder,
PageTitle = pageTitle,
PageBody = pageBody,
UserID = userID
};
try
{
await _pagesDBContext.Pages.AddAsync(newPage);
await _pagesDBContext.SaveChangesAsync();
}
catch (Exception e)
{
string message = "ERROR: Could not save to the database.\n";
return message + e.Message;
}
return "Page saved";
}
return "ERROR: Model invalid";
}
I am sending the request as simple text. I don't know if there is a better way.
Any ideas?
I don't have enough rep to comment but it looks like the maximum characters you can send in a GET request is 2,048.

How to test a controller POST method which returns no data in response content in .NET Core 3.1?

i am new to integration tests. I have a controller method which adds a user to the database, as shown below:
[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
try
{
var command = new CreateUserCommand
{
Login = request.Login,
Password = request.Password,
FirstName = request.FirstName,
LastName = request.LastName,
MailAddress = request.MailAddress,
TokenOwnerInformation = User
};
await CommandBus.SendAsync(command);
return Ok();
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
As you have noticed my method returns no information about the user which has been added to the database - it informs about the results of handling a certain request using the status codes. I have written an integration test to check is it working properly:
[Fact]
public async Task ShouldCreateUser()
{
// Arrange
var createUserRequest = new CreateUserRequest
{
Login = "testowyLogin",
Password = "testoweHaslo",
FirstName = "Aleksander",
LastName = "Kowalski",
MailAddress = "akowalski#onet.poczta.pl"
};
var serializedCreateUserRequest = SerializeObject(createUserRequest);
// Act
var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
serializedCreateUserRequest);
// Assert
response
.StatusCode
.Should()
.Be(HttpStatusCode.OK);
}
I am not sure is it enough to assert just a status code of response returned from the server. I am confused because, i don't know, shall i attach to assert section code, which would get all the users and check does it contain created user for example. I don't even have any id of such a user because my application finds a new id for the user while adding him/her to the database. I also have no idea how to test methods like that:
[HttpGet("{userId:int}")]
public async Task<IActionResult> GetUserAsync([FromRoute] int userId)
{
try
{
var query = new GetUserQuery
{
UserId = userId,
TokenOwnerInformation = User
};
var user = await QueryBus
.SendAsync<GetUserQuery, UserDto>(query);
var result = user is null
? (IActionResult) NotFound(new
{
Message = (string) _stringLocalizer[UserConstants.UserNotFoundMessageKey]
})
: Ok(user);
return result;
}
catch (Exception e)
{
await HandleExceptionAsync(e);
return StatusCode(StatusCodes.Status500InternalServerError,
new {e.Message});
}
}
I believe i should somehow create a user firstly in Arrange section, get it's id and then use it in Act section with the GetUserAsync method called with the request sent by HttpClient. Again the same problem - no information about user is returned, after creation (by the way - it is not returned, because of my CQRS design in whole application - commands return no information). Could you please explain me how to write such a tests properly? Have i missed anything? Thanks for any help.
This is how I do it:
var response = (CreatedResult) await _controller.Post(createUserRequest);
response.StatusCode.Should().Be(StatusCodes.Status201Created);
The second line above is not necessary, just there for illustration.
Also, your response it's better when you return a 201 (Created) instead of the 200(OK) on Post verbs, like:
return Created($"api/users/{user.id}", user);
To test NotFound's:
var result = (NotFoundObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status404NotFound);
The NotFoundObjectResult assumes you are returning something. If you are just responding with a 404 and no explanation, replace NotFoundObjectResult with a NotFoundResult.
And finally InternalServerErrors:
var result = (ObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);
You can use integrationFixture for that using this NuGet package. This is an AutoFixture alternative for integration tests.
The documented examples use Get calls but you can do other calls too. Logically, you should test for the status code (OkObjectResult means 200) value and the response (which could be an empty string, that is no problem at all).
Here is the documented example for a normal Get call.
[Fact]
public async Task GetTest()
{
// arrange
using (var fixture = new Fixture<Startup>())
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var controller = fixture.Create<SearchEngineController>();
// act
var response = await controller.GetNumberOfCharacters("Hoi");
// assert
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
Assert.Equal(8, ((OkObjectResult)response.Result).Value);
}
}
}
private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
fluentMockServer.Given(Request.Create().UsingGet())
.RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
.WithStatusCode(HttpStatusCode.OK));
}
In the example above, the controller is resolved using the DI described in your Startup class.
You can also do an actual REST call using using Refit. The application is self hosted inside your test.
using (var fixture = new RefitFixture<Startup, ISearchEngine>(RestService.For<ISearchEngine>))
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var refitClient = fixture.GetRefitClient();
var response = await refitClient.GetNumberOfCharacters("Hoi");
await response.EnsureSuccessStatusCodeAsync();
var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
Assert.Contains("Hoi", request.RawQuery);
}
}

client.GetAsync doesn't refresh my data

I develop au Universal App using MVVM-Light.
On a page, there is a list of comments coming from a WebService. If the current user is the author of a comment, I show a FlyoutMenu allowing him to "Edit" or "Delete" its comment. There is also a AppBarButton for adding a new comment:
My problem is that the comments are never refreshed after the first load of this page...
I use a "LoadComments()" method in the ViewModel that allows me to get the comments when I arrive on the page, but also after editing, deleted or added an item:
private async void LoadComments()
{
List<Comment> commentsWS = await WebServiceGetCommentList();
if (commentsWS != null)
Comments = new ObservableCollection<Commentaire>(commentsWS);
}
This method so calls another method "WebServiceGetCommentList()" that prepares the call to the WebService, in the same ViewModel:
private async Task<List<Comment>> WebServiceGetCommentList()
{
// Parameters
List<KeyValuePair<string, string>> parametres = new List<KeyValuePair<string, string>>();
parametres.Add(new KeyValuePair<string, string>("option", _currentUserAccount.option));
parametres.Add(new KeyValuePair<string, string>("id_article", Article.id_article.ToString()));
// Call WebService and deserialize
Exception custEx = null;
try
{
List<Comment> comments = await WebServices.GetCommentList(_currentUserAccount.url, parametres, "");
return comments;
}
// Exceptions
catch (Exception e)
{
...
}
return null;
}
I then go in the "GetComments()" method on the "WebServices" class:
public static async Task<List<Comment>> GetCommentList(String url, List<KeyValuePair<String, String>> parametres, String protocol)
{
// Call WebService and deserialize
var response = await JSONParser.getJSONFromUrl(url, parametres, "");
List<Comment> comments = new List<Comment>();
WsResponse wsResponse = ManageWsReponse(response, Constants.WebServiceTask.GetCommentList.Value);
try
{
WsResponseResult wsResponseResult = JsonConvert.DeserializeObject<WsResponseResult>(wsResponse.data.ToString());
comments = JsonConvert.DeserializeObject<List<Comment>>(wsResponseResult.result.ToString());
return comments;
}
catch (Exception e)
{
throw new DeserializeException("Deserialize exception", e, DateTime.Now, "Comment");
}
}
This method calls the "getJSONFromUrl()" method in the "JSONParser" class that launches the "client.GetAsync()":
public static async Task<string> getJSONFromUrl(String url, List<KeyValuePair<String, String>> parameters, String protocol)
{
var client = new HttpClient();
// Preparing URI
string sParameters = null;
int i = 1;
foreach (var param in parameters)
{
sParameters += param.Key + "=" + param.Value;
sParameters += i != parameters.Count ? "&" : string.Empty;
i++;
}
var uri = new Uri(url + "?" + sParameters);
// Calls the WebService
var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
// Code and results
var statusCode = response.StatusCode;
// EnsureSuccessStatusCode throws exception if not HTTP 200
response.EnsureSuccessStatusCode();
// responseText
var responseText = await response.Content.ReadAsStringAsync();
return responseText;
}
I can add, delete or edit a comment with success, but when I'm back to this method "LoadComments()", the changes are not taken into account, and I get the same list than at the first call...
I also placed breakpoints in the "GetJSONFromURL()" method and I don't see the added, deleted or edited comments in the response var.
In the same time, if I copy the URI in a brower, for calling the same WebService with the same parameters, the changes are taken into account.
=> I think so there is a caching on client.GetAsync(), but I don't see how to desactive it, or force it to refresh datas...
I tried this solution httpclient-caching that doesn't work for me.
I think so that there is cache managing cause when I
That's the platform caching. I haven't had any success with the Cache-Control headers, the most reliable way to address the issue is to make sure the request is different.
Add an additional parameter - a timestamp. As the request is different, the platform cannot used the cached response.
parametres.Add(new KeyValuePair<string, string>("mytmstmp", DateTime.Now.Ticks);
Or: Use an additional header that allows for a date. I've used:
"If-Modified-Since", DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)

Can't Delete Facebook Requests (user-to-user or app-to-user request) with Facebook C# SDK or with HTTPWebRequest

I have tried almost every variation I can think of but nothing seems to work. I am trying to simply delete a Facebook request (user-to-user or app-to-user request) using the delete method of the C# SDK or using the HTTPWebRequest method. I am grabbing the "request_ids" querystring parameter from the URL when a user clicks on a request and using that for the method along the Facebook User ID and the App Access Token, both of both of which are stored in session variables. Below are some of the variations have I tried (did not include a few) but none of these work for some reason. I am using ASP.NET 3.5 so the C# SDK I have is the ASP.NET 3.5 version (can't use dynamic types). Any help is appreciated!
Option #1
public static void deleteFbRequest(string facebookRequestId, string appAccessToken, string fbuserid)
{
var fb = new FacebookClient();
var url = "https://graph.facebook.com/" + fbuserid + "?access_token=" + appAccessToken;
fb.Delete((String.Format(url, facebookRequestId + "_" + fbuserid, fb.AccessToken)));
}
Option #2
public static void deleteFbRequest(string facebookRequestId, string appAccessToken, string fbuserid)
{
var fb = new FacebookClient();
var result = fb.Delete(facebookRequestId.ToString() + "_" + fbuserid.ToString());
}
Option #3
public static void deleteFbRequest(string facebookRequestId, string appAccessToken, string fbuserid)
{
var fb = new FacebookClient(appAccessToken);
var parameters = new Dictionary<string, object>();
parameters["access_token"] = appAccessToken;
parameters["method"] = "DELETE";
var result = fb.Delete("/" + facebookRequestId + "_" + fbuserid, parameters);
}
Option #4
public static void deleteFbRequest(string facebookRequestId, string appAccessToken, string fbuserid)
{
string url = "";
url = "https://graph.facebook.com/" + facebookRequestId + "_" + fbuserid + "?access_token=" + appAccessToken + "&method=DELETE";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Proxy = null;
}
OK, it's always the little things that cause bugs. After racking my brain for ours, the issue was the session variables I used for the method parameters had whitespace at the end of the them (extra spaces). So I used the trim method and it works (see below):
Session["FacebookUserID"].ToString().Trim()
Geez!!

Categories

Resources