I tried make it as you suggested (answer below this). Unfortunately still I have this problem.
This is my new TokenService.cs:
class TokenService
{
TokenKeeper tokenkeeper;
public TokenService()
{
tokenkeeper = new TokenKeeper();
}
public async void AwaitGetToken()
{
tokenkeeper = new TokenKeeper();
tokenkeeper = await GetToken();
}
private async Task<TokenKeeper> GetToken()
{
string stringuri = string.Format("{0}/token", RestAccess.HostUrl);
var dict = new Dictionary<string, string>();
dict.Add("grant_type", "password");
dict.Add("username", RestAccess.User);
dict.Add("password", RestAccess.Pass);
dict.Add("client_id", RestAccess.Client_ID);
dict.Add("client_secret", RestAccess.Client_secret);
tokenkeeper=new TokenKeeper();
var httpclient = new HttpClient();
try
{
var req = new HttpRequestMessage(HttpMethod.Post, stringuri) { Content = new FormUrlEncodedContent(dict) };
var res = await httpclient.SendAsync(req);
if (res.IsSuccessStatusCode)
{
var content = await res.Content.ReadAsStringAsync();
tokenkeeper = JsonConvert.DeserializeObject<TokenKeeper>(content);
return tokenkeeper;
}
return tokenkeeper;
}
catch
{
return tokenkeeper;
}
finally
{
httpclient.CancelPendingRequests();
httpclient.Dispose();
}
}
}
My tokenkeeper is simple class:
public class TokenKeeper
{
public string access_token { get; set; }
public string refresh_token { get; set; }
public TokenKeeper()
{
access_token = "";
refresh_token = "";
}
}
In my calling code I have as below:
...
tokenservice.AwaitGetToken();
GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
_navigationService.NavigateAsync("ZleceniaListContentPage", par);
...
tokenservice.AwaitGetToken() and GEtXFMapUserFromAzureAndSetVariable(RestAccess.User) they are similar. Both await but tokenservice.AwaitGetToken() is POST and GEtXFMapUserFromAzureAndSetVariable is GET.
GEtXFMapUserFromAzureAndSetVariable is working correctly but if I call tokenservice.AwaitGetToken() the aplication get the token but before it's continue. At the end the instruction GEtXFMapUserFromAzureAndSetVariable it is being done tokenservice.AwaitGetToken() responds.
How can I wait with call GEtXFMapUserFromAzureAndSetVariable until I receive a reply from tokenservice.AwaitGetToken() ???
Delete your AwaitGetToken method, it is useless.
Now make you GetToken method public and call it with await:
await tokenService.GetToken();
await GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
await _navigationService.NavigateAsync("ZleceniaListContentPage", par);
Basically you always need to await methods returning Task, or it will run in a parallel fashion, and you will lose consistency in your code.
You seem very confused by Task and async/await, I will strongly advise you to read tutorials and docs on it:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
Related
I'm using the packages "FastEndpoints" & "FastEndpoints.Security" for creating the RESTApi.
This is my Endpoint:
public class LoginEndpoint : Endpoint<LoginRequest, LoginResponse>
{
IUserService _userSvc;
public LoginEndpoint(IUserService users)
{
_userSvc = users;
}
public override void Configure()
{
Verbs(Http.GET);
Routes("/api/login");
AllowAnonymous();
}
public override async Task HandleAsync(LoginRequest req, CancellationToken ct)
{
if (_userSvc.LoginValidByName(req.Name, req.Password))
{
var user = _userSvc.GetFromName(req.Name);
var expiresAt = DateTime.UtcNow.AddDays(1);
var token = JWTBearer.CreateToken(
GlobalSettings.TOKEN_SIGNING_KEY,
expiresAt,
user.Permissions.Select(p => p.Name));
await SendAsync(
new LoginResponse()
{
Token = token,
ExpiresAt = expiresAt
});
}
else
{
await SendUnauthorizedAsync();
}
}
}
Using Postman, the endpoints works as expected:
But when using RestSharp (and mind you, I'm very new to the whole RESTApi world), I get an error 'Request ended prematurely'.
This is my simple call:
public class ApiClient
{
private RestClient _restClient;
public ApiClient(string baseUrl)
{
_restClient = new RestClient(baseUrl);
//ServicePointManager.ServerCertificateValidationCallback += (s, c, ch, p) => true;
}
public async Task<bool> UserValid(string username, string password)
{
var request = new RestRequest("/api/login", Method.Get);
request.AddParameter("name", username);
request.AddParameter("password", password);
var result = await _restClient.GetAsync(request);
if (result.StatusCode == HttpStatusCode.OK)
return true;
else
return false;
}
}
Can someone fill me in?
Since it works with Postman, I suspect my call being bad.
Is:
_userSvc.LoginValidByName
Or any other function missing an await by chance?
I have a little program which should communicate with "Slack". In an older Version I used "Dictionary<string, string>" and then put them into UrlEncodedContent - which worked fine.
Now I am trying to create a Json-object, using Newtonsoft's Nuget-package and (in my opinion) formatting my object the way they say on their website.
Problem is, when I try to make a simple request, my program just runs to one specific line in the code(var response = await _httpClient.SendAsync(request);) and then it just ends. It doesn't throw an exception or display any kind of message, it simply ends on this line. I went through my code step by step while debugging, that's how I know it ends on exactly this line. And I just don't know why!
Now my code:
First, my object...
namespace BPS.Slack
{
public class JsonObject
{
//generally needed parameters
[JsonProperty("ok")]
public bool ok { get; set; }
[JsonProperty("error")]
public string error { get; set; }
[JsonProperty("channel")]
public string channel { get; set; }
[JsonProperty("token")]
private string token = "xoxp-MyToken";
[JsonProperty("as_user")]
public bool as_user = false;
[JsonProperty("username")]
public string username { get;set; }
//--------------------------------
//only needed for textmessages
[JsonProperty("text")]
public string text { get; set; }
//--------------------------------
//for posting messages with data attached
[JsonProperty("initial_comment")]
public string initial_comment { get; set; }
[JsonProperty("file")]
public string file { get; set; }
[JsonProperty("channels")]
public string channels { get; set; }
//--------------------------------
//for getting the latest message from a channel
[JsonProperty("count")]
public string count = "1";
[JsonProperty("unreads")]
public bool unreads = true;
}
}
now the client:
namespace BPS.Slack
{
public class BpsHttpClient
{
private readonly HttpClient _httpClient = new HttpClient { };
public Uri UriMethod { get; set; }
public BpsHttpClient(string webhookUrl)
{
UriMethod = new Uri(webhookUrl);
}
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
}
}
and the main
namespace TestArea
{
class MainArea
{
public static void Main( string[] args)
{
try
{
Task.WhenAll(SendMessage());
}
catch(Exception ass)
{
Console.WriteLine(ass);
Console.ReadKey();
}
}
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(StringJson);
var Response = await client.UploadFileAsync(content);
string AnswerContent = await Response.Content.ReadAsStringAsync();
Console.WriteLine(AnswerContent);
Console.ReadKey();
}
}
}
I had the same problem in my older version, BUT only as I wanted to DEserialize an answer I got from Slack. It had to do with my object I tried do deserialize the answer into. But this time I can not figure out what's wrong. But, as I said, I do not have any experience with using serialized objects as Json-property to send requests... anyone has an idea what is wrong with my code?
EDIT: This problem is kinda solved. But there is a follow up problem.
Okay, I found out that the reason for the abprubt termination was the
Task.WhenAll(SendMessage());
it should be
Task.WaitAll(SendMessage()); Why??? Somebody said I should use WhenAll, but obviously it doesn't work properly in this case...
Now I get a response from Slack, but now a different problem has arisen. When I use this method:
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
I allways get the answer:
{"ok":false,"error":"invalid_form_data"}
so I tried to explicitly tell it the 'mediaType', I tried "application/json" and others, but with all of them I get the same error. Here is the full method that calls the upper mehtod:
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/chat.postMessage");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
JO.text = "This is so much fun :D !";
var Json = JsonConvert.SerializeObject(JO, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var StringJson = new StringContent(Json, Encoding.UTF8, "application/json");
var requestContent = new MultipartFormDataContent();
requestContent.Add(StringJson);
var Response = await client.UploadFileAsync(requestContent);
string AnswerContent = await Response.Content.ReadAsStringAsync();
}
When I use this method:
public async Task<HttpResponseMessage> SendMessageAsync(FormUrlEncodedContent content)
{
var response = await _httpClient.PostAsync(UriMethod, content);
return response;
}
so bascially I am passing "FormUrlEncodedContent" instead of "MultipartFormDataContent" in this, and then I get the response I want and can work wiht it. BUT this i of little use to me since I have to use "MultipartFormDataContent" to be able to send files with my requests.
Anyone have an idea what is failing here? Why does it not like the one content-type but the other one? I'd be gratefull for tipps and ideas!
You are serializing your object to Json and then adding it to a Multipart body, that's quite strange. Unless you're uploading binary data (eg Files), there is no need to use MultipartFormDataContent.
You are can directly post your JsonObject serialized as JSON:
public async Task<HttpResponseMessage> PostJsonAsync(StringContent content)
{
var response = await client.PostAsync(url, content);
return response;
}
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
var Response = await client.PostJsonAsync(content);
Also this is should be POST on the UploadFileAsync function.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);
so I figured out that in the Main() the problem was this:
Task.WhenAll(SendMessage());
I should instead use:
Task.WaitAll(SendMessage());
Anyone who has more knowledge on this, please elaborate why!
I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.
I am not familiar with threading in .NET.
I have an ansync method MyTest:
public async Task MyTest() {
using (HttpClient httpClient = new HttpClient()) {
httpClient.BaseAddress = new Uri(_uri);
var response = await httpClient.GetAsync("API/GetData");
if(response!=null && response.IsSuccessStatusCode) {
var json = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
}
}
}
The problem that I am running into is calling the method to get the results (Dictionary).
When I step though my code I am seeing the IsCompleted is done before the results come back from my rest call.
How do I properly use threading in this case?
My method to call the async method.
public void GetTestData()
{
try
{
ARestService rest = new ARestService();
Task tsk = new Task(rest.MyTest);
if (tsk.IsCompleted)
{
var tst = "Done?";
}
}
catch(Exception ex)
{
string a = ex.Message;
}
}
If you can turn your GetTestData method into an async method simply do this.
public async Task GetTestData()
{
try
{
ARestService rest = new ARestService();
await rest.MyTest();
var tst = "Done?";
}
catch(Exception ex)
{
string a = ex.Message;
}
}
You should also define that your method returns a Task<Dictionary<string, string>> to receive the result of your rest service call, with the following statement.
Dictionary<string, string> dict = await rest.MyTest();
If not, have a look at some workarounds, like using GetAwaiter().GetResult() but as explained in this question Is .GetAwaiter().GetResult(); safe for general use? it can cause some problems, so the best option is to make your calling code async too.
The sample code for working with KeyVault inside a web application has the following code in it:
public static async Task<string> GetSecret(string secretId)
{
var secret = await keyVaultClient.GetSecretAsync(secretId);
return secret.Value;
}
I've incorporated the KeyVaultAccessor object included in the sample in my application in order to test it. The call is executed as part of a query to one of my web api controller methods:
var secret = KeyVaultAccessor.GetSecret("https://superSecretUri").Result;
Unfortunately, the call never returns and the query hangs indefintely...
What might be the reason, because frankly I have no clue where to start...?
This is the common deadlock issue that I describe in full on my blog. In short, the async method is attempting to return to the ASP.NET request context after the await completes, but that request only allows one thread at a time, and there is already a thread in that context (the one blocked on the call to Result). So the task is waiting for the context to be free, and the thread is blocking the context until the task completes: deadlock.
The proper solution is to use await instead of Result:
var secret = await KeyVaultAccessor.GetSecret("https://superSecretUri");
I've used the following code to override the synchronization context:
var secret = Task.Run(async () => await KeyVaultAccessor.GetSecretAsync("https://superSecretUri")).Result;
This still lets you use .Result if you're in a non-async method
Unfortunately, the call never returns and the query hangs indefinitely...
You have a classic deadlock. That's why you shouldn't block on async code. Behind the scenes, the compiler generates a state-machine and captures something called a SynchronizationContext. When you synchronously block the calling thread, the attempt to post the continuation back onto that same context causes the deadlock.
Instead of synchronously blocking with .Result, make your controller async and await on the Task returned from GetSecret:
public async IHttpActionResult FooAsync()
{
var secret = await KeyVaultAccessor.GetSecretAsync("https://superSecretUri");
return Ok();
}
Side note - Async methods should follow naming conventions and be postfixed with Async.
Use the rest api...
public class AzureKeyVaultClient
{
public string GetSecret(string name, string vault)
{
var client = new RestClient($"https://{vault}.vault.azure.net/");
client.Authenticator = new AzureAuthenticator($"https://vault.azure.net");
var request = new RestRequest($"secrets/{name}?api-version=2016-10-01");
request.Method = Method.GET;
var result = client.Execute(request);
if (result.StatusCode != HttpStatusCode.OK)
{
Trace.TraceInformation($"Unable to retrieve {name} from {vault} with response {result.Content}");
var exception = GetKeyVaultErrorFromResponse(result.Content);
throw exception;
}
else
{
return GetValueFromResponse(result.Content);
}
}
public string GetValueFromResponse(string content)
{
var result = content.FromJson<keyvaultresponse>();
return result.value;
}
public Exception GetKeyVaultErrorFromResponse(string content)
{
try
{
var result = content.FromJson<keyvautlerrorresponse>();
var exception = new Exception($"{result.error.code} {result.error.message}");
if(result.error.innererror!=null)
{
var innerException = new Exception($"{result.error.innererror.code} {result.error.innererror.message}");
}
return exception;
}
catch(Exception e)
{
return e;
}
}
class keyvaultresponse
{
public string value { get; set; }
public string contentType { get; set; }
}
class keyvautlerrorresponse
{
public keyvaulterror error {get;set;}
}
class keyvaulterror
{
public string code { get; set; }
public string message { get; set; }
public keyvaulterror innererror { get; set; }
}
class AzureAuthenticator : IAuthenticator
{
private string _authority;
private string _clientId;
private string _clientSecret;
private string _resource;
public AzureAuthenticator(string resource)
{
_authority = WebConfigurationManager.AppSettings["azure:Authority"];
_clientId = WebConfigurationManager.AppSettings["azure:ClientId"];
_clientSecret = WebConfigurationManager.AppSettings["azure:ClientSecret"];
_resource = resource;
}
public AzureAuthenticator(string resource, string tennant, string clientid, string secret)
{
//https://login.microsoftonline.com/<tennant>/oauth2/oken
_authority = authority;
//azure client id (web app or native app
_clientId = clientid;
//azure client secret
_clientSecret = secret;
//vault.azure.net
_resource = resource;
}
public void Authenticate(IRestClient client, IRestRequest request)
{
var token = GetS2SAccessTokenForProdMSA().AccessToken;
//Trace.WriteLine(String.Format("obtained bearer token {0} from ADAL and adding to rest request",token));
request.AddHeader("Authorization", String.Format("Bearer {0}", token));
}
public AuthenticationResult GetS2SAccessTokenForProdMSA()
{
return GetS2SAccessToken(_authority, _resource, _clientId, _clientSecret);
}
private AuthenticationResult GetS2SAccessToken(string authority, string resource, string clientId, string clientSecret)
{
var clientCredential = new ClientCredential(clientId, clientSecret);
AuthenticationContext context = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = context.AcquireToken(
resource,
clientCredential);
return authenticationResult;
}
}
}
This generic method can be used to override the deadlock issue:
public static T SafeAwaitResult<T>(Func<Task<T>> f)
{
return Task.Run(async () => await f()).Result;
}
Use:
SafeAwaitResult(() => foo(param1, param2));