GetAsync azure call no result - c#

Using VS 2017 Community. Azure.
I have Azure setup, I have a blank webapp created just for test purpose.
My actual site is an Angular2 MVC5 site, currently run locally.
The following is the code that should... Contact azure providing secret key(the site is registered in azure Active directory).
From this i get a token i then can use to contact azure api and get list of sites.
WARNING: code is all Sausage code/prototype.
Controller
public ActionResult Index()
{
try
{
MainAsync().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
static async System.Threading.Tasks.Task MainAsync()
{
string tenantId = ConfigurationManager.AppSettings["AzureTenantId"];
string clientId = ConfigurationManager.AppSettings["AzureClientId"];
string clientSecret = ConfigurationManager.AppSettings["AzureClientSecret"];
string token = await AuthenticationHelpers.AcquireTokenBySPN(tenantId, clientId, clientSecret).ConfigureAwait(false);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
client.BaseAddress = new Uri("https://management.azure.com/");
await MakeARMRequests(client);
}
}
static async System.Threading.Tasks.Task MakeARMRequests(HttpClient client)
{
const string ResourceGroup = "ProtoTSresGrp1";
// Create the resource group
// List the Web Apps and their host names
using (var response = await client.GetAsync(
$"/subscriptions/{Subscription}/resourceGroups/{ResourceGroup}/providers/Microsoft.Web/sites?api-version=2015-08-01"))
{
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
foreach (var app in json.value)
{
Console.WriteLine(app.name);
foreach (var hostname in app.properties.enabledHostNames)
{
Console.WriteLine(" " + hostname);
}
}
}
}
Controller class uses a static helper class that gets the token from Azure...
public static class AuthenticationHelpers
{
const string ARMResource = "https://management.core.windows.net/";
const string TokenEndpoint = "https://login.windows.net/{0}/oauth2/token";
const string SPNPayload = "resource={0}&client_id={1}&grant_type=client_credentials&client_secret={2}";
public static async Task<string> AcquireTokenBySPN(string tenantId, string clientId, string clientSecret)
{
var payload = String.Format(SPNPayload,
WebUtility.UrlEncode(ARMResource),
WebUtility.UrlEncode(clientId),
WebUtility.UrlEncode(clientSecret));
var body = await HttpPost(tenantId, payload).ConfigureAwait(false);
return body.access_token;
}
static async Task<dynamic> HttpPost(string tenantId, string payload)
{
using (var client = new HttpClient())
{
var address = String.Format(TokenEndpoint, tenantId);
var content = new StringContent(payload, Encoding.UTF8, "application/x-www-form-urlencoded");
using (var response = await client.PostAsync(address, content).ConfigureAwait(false))
{
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("Status: {0}", response.StatusCode);
Console.WriteLine("Content: {0}", await response.Content.ReadAsStringAsync());
}
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<dynamic>().ConfigureAwait(false);
}
}
}
}
ISSUE:
Ok so the issue I was faced with was Async Deadlocks in my code. So i looked at this stack post stack post here
I fixed the issues by putting in .ConfigureAwait(false) on most of the await declarations.
Code runs and gets all the way back to the controller with a token etc and runs through the MakeARMRequests(HttpClient client) method, however the json only returns 1 result "{[]}" when i debug and as such ignores the loops.
My question is, is my code the culprit here? or would this point to a configuration setting in azure?

Not sure if this is the issue you are facing now BUT you never wait for a result from your async action in the first method Index in your code. MainAsync().ConfigureAwait(false); will immediately return and continue to the next block while the task MainAsync() will start in the background. The catch handler also does nothing because you dont wait f or a result.
Option 1 (recommended)
public async Task<ActionResult> Index()
{
try
{
await MainAsync().ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}
Option 2 if you can't use async/await for some reason
public ActionResult Index()
{
try
{
MainAsync().GetAwaiter().GetResult();
}
catch (Exception e)
{
Console.WriteLine(e.GetBaseException().Message);
}
return View();
}

The Code looks OK and runs fine, Anyone who could help verify would be good, but one can assume this is OK.
The issue for this was configuration in azure, When you register an app you must set a certain number of Access controls via the subscription.
In this case I set some more specific things for the web api , for now set the app as owner and made reference to service management api.
Probably don't need half the "IAM" added in the subscription to the registered app, I simply went through adding the relevant ones and debugging each time until finally i got the results expected.

Related

ContinueWith doesn't work in Controller to log

I can't find a solution to the problem despite many similar questions.
There is a Web API. On POST I need
read DB
make a HTTP call to other service to subscribe on notification (let's say it takes 5s)
return the data from the DB
In the step 2, I don't need to wait, I don't need to block the client (for 5sec), so the client should not wait for the response.
However, the server have to wait on result from 2 and log it. So far I've tried
[HttpPost("{callId}")]
public async Task<IActionResult> CreateSubs([FromRoute] string callId)
{
var data = await ...// read the DB
_ = SubscribeForUpdates(callId);
return Ok(data);
}
private async Task SubscribeForUpdates(string callId)
{
_logger.LogInformation("Subscribe client {ConnectionId} notifications", callId);
var requestMessage = new HttpRequestMessage
{
RequestUri = new Uri(_httpClient.BaseAddress, $"subscribe/{callId}"),
Method = HttpMethod.Get,
};
var result = await SendAsync<SubscriptionResponse>(requestMessage);
if (result.IsSuccess)
{
Console.WriteLine("Success");
}
else
{
Console.WriteLine("Fail");
}
}
SendAsync is from some library and so smth like _httpClient.SendAsync
In this case the request will not be blocked, the internal HTTP request is successful but I there is no Success from Console.WriteLine("Success");. Only if I put a breakpoint there it logs.
Could you please help me to understand why this is not log and how to fix that?
I've tried ContinueWith - no result
await SendAsync<ServerSubscriptionResponse>(requestMessage)
.ContinueWith(t =>
{
if (t.Result.IsSuccess)
{
Console.WriteLine("Success");
}
else
{
Console.WriteLine("Fail");
}
})
When I use await SubscribeForUpdates(callId) inasted of _ = SubscribeForUpdates(callId) it works and logs but the blocks a client. I need to avoid that

Asp.Net Core Web API returns the result while client is hanging

I have a simple Asp.Net Core Web API with .NET 6, running on IIS 10, Windows 10. The web API calls another API and returns the results. Below is a simplified version of its code but I tried to keep the most important parts.
[ApiController]
[Produces("application/json")]
public class SomeController
{
private async Task<ApiOutput> RunApiForClientAsync(ApiInput input)
{
try
{
//create a httpclient with a lot of configuration
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return new ApiOutput
{
Data = content,
Error = null,
StatusCode = 200,
Input = input,
};
}
catch(Exception ex)
{
return new ApiOutput
{
Data = null,
Error = new ApiError("Error Getting the Result from the Server", ex.Message),
StatusCode = 400,
Input = input,
};
}
}
private async Task<List<ApiOutput>> RunApiCallsAsync(string requestId, IEnumerable<ApiInput> items)
{
var result = new List<ApiOutput>();
var tasks = new List<Task<ApiOutput>>();
foreach(var item in items)
{
tasks.Add(RunApiForAsync(item));
}
var taskResults = await Task.WhenAll(tasks);
result.AddRange(taskResults);
return result;
}
[HttpPost]
[Route("rest/multiple")]
public async Task<IActionResult> PostMultiple(ApiInput[] models, string? requestId)
{
_logger.LogInformation(ApiLoggingEvents.PostMultiple, "Request received with ID {requestId}", requestId);
var result = await RunApiCallsAsync(requestId, models);
try
{
_logger.LogDebug(ApiLoggingEvents.PostMultiple, "Request ID {requestId} Generating JSONs.", requestId);
var resultJson = GetJson(result);
await SaveResultAsync(resultJson, requestId);
_logger.LogDebug(ApiLoggingEvents.PostMultiple, "Request ID {requestId} Everything is finished. Returning....", requestId);
return Content(resultJson, "application/json");
}
catch (Exception ex)
{
_logger.LogDebug(ApiLoggingEvents.PostMultiple, "Exception while returning {requestId}, message {msg}", requestId, ex.Message);
throw new Exception("Try again");
}
}
}
Every once in a while, the caller sends the request to the API but never gets the result back. However, when I read the logs, I see the last line for the request is the line containing the text "Everything is finished. Returning" which means everything was successful. In addition, the output JSON is saved on the server's local drive (the await SaveResultAsync(resultJson, requestId); call is successful too).
I should mention that these types of requests are the ones that take a long while to respond. Usually around 10 minutes. Is there a setting that I need to change on the application or the IIS?
I tried to use the following but it doesn't work with the In-Process model:
builder.WebHost.UseKestrel(o =>
{
o.Limits.MaxConcurrentConnections = 100;
o.Limits.KeepAliveTimeout = TimeSpan.FromMilliseconds(timeout);
o.Limits.MaxRequestBodySize = int.MaxValue;
o.Limits.MaxResponseBufferSize = int.MaxValue;
});
Note:
The requestId is a unique GUID for every request to help me keep track of each request on the log file and see whether it was successful or not and if it has created the output file.
Update:
Upon further investigation, it seems like the requests that have a runtime more than 5 minutes are failing. Any idea what might be related to this number?
Update 2:
I created a very simple endpoint that waits for a specified amount of seconds, then returns back with a simple message:
[HttpPost]
[Route("rest/testpost")]
public IActionResult TestPost(int delay)
{
_logger.LogInformation(1, "Delay for {delay} started.", delay);
Thread.Sleep(delay * 1000);
_logger.LogInformation(1, "Delay for {delay} ended.", delay);
return Ok($"Delay for {delay} worked.");
}
I then added the requestTimeout="00:20:00" to the web.config file, just to make sure.
Interestingly, for values such as 310 seconds, sometimes I get the result, but sometimes I don't. (Postman still hangs)
To your web.config, add the following (the value is in seconds--so this will allow runtimes up to 20 minutes):
<system.web>
<httpRuntime executionTimeout="1200" />
</system.web>
Here is a link to the documentation: https://learn.microsoft.com/en-us/dotnet/api/system.web.configuration.httpruntimesection.executiontimeout?view=netframework-4.8

How to cancel async Task from the client

I have on ASP.Net C# web API with an endpoint for the import. Javascript client sends a list of items to this API and API process this list in another thread (long task) and immediately returns unique id (GUID) of process. Now I need the cancel the background task from the CLIENT. Is possible to somehow send the cancelation token from the client? I have tried to add CancellationToken as a parameter to my controller async action but I don't know how to pass it from the client. For simplification, we can use as the client the Postman app.
Sample server-side
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
foreach (var item in queue)
{
var result = await ProcessItemList(item, false);
resultWrapper.AddResultItem(result);
}
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
On a high level what you could do is store the process id along with a cancellation token source when you queue the work. Then you can expose a new endpoint that accepts a process id, gets the cancellation token source from the store and cancels the associated token:
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
var lts = CancellationTokenSource.CreateLinkedTokenSource(ct);
var ct = lts.Token;
TokenStore.Store(resultWrapper.ProcessId, lts);
foreach (var item in queue)
{
var result = await ProcessItemList(item, ct, false);
resultWrapper.AddResultItem(result);
}
TokenStore.Remove(processId) // remove the cancellation token source from storage when doen, because there is nothing to cancel
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, CancellationToken token, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList, token);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
[Route("api/bulk/CancelImportAsync")]
public async Task<IHttpActionResult> CancelImportAsync(Guid processId)
{
var tokenSource = TokenStore.Get(processId);
tokenSource.Cancel();
TokenStore.Remove(processId) // remove the cancellation token source from storage when cancelled
}
In the above example I modified the ProcessItemList to accept a cancellation token and pass it to PerformBulkOperation, assuming that method has support for cancellation tokens. If not, you can manually call ThrowIfCancellationRequested(); on the cancellation token at certain points in the code to stop when cancellation is requested.
I've added a new endpoint that allows you to cancel a pending operation.
Disclaimer
There are for sure some things you need to think about, especially when it is a public api. You can extend the store to accepts some kind of security token and when cancellation is requested you check whether it matches with the security token that queued the work. My answer is focused on the basics of the question
Also, I left the implementation of the store to your own imagination ;-)

HttpClient PostAsync not working in CRM plugin

I am trying to send json to a web API using HttpClient.PostAsync. It works from a console application but not from my CRM plugin. Doing some research I noted that it is probably something to do with the context the plugin runs in and threading. Anyway here is my calling code:
public async void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
if (context.InputParameters.Contains("Target"))
{
if (context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == "new_product")
{
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
try
{
if (entity.Contains("new_begindate") && entity.Contains("new_expirationdate"))
{
await OnlineSignUp(entity, service);
}
}
catch (InvalidPluginExecutionException)
{
throw;
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(OperationStatus.Failed, "Error signing up: " + e.Message);
}
}
}
}
}
And here is the relevant code for sending the json:
private async Task<HttpResponseMessage> OnlineSignUp(Entity license, IOrganizationService service)
{
...
var json = JsonConvert.Serialize(invitation);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", "token=7d20f3f09ef24067ae64f4323bc95163");
Uri uri = new Uri("http://signup.com/api/v1/user_invitations");
var response = await httpClient.PostAsync(uri, content).ConfigureAwait(false);
int n = 1;
return response;
}
}
The exception is thrown with a message "Thread was being aborted". Can anyone tell me what I am doing wrong?
I would guess this is going to fail somewhat randomly based on use of async/await. I wouldn't think CRM really supports plugins returning control before they are complete. When it fails, it looks like the thread was in the process of being cleaned up behind the scenes.
CRM is already handling multi-threading of plugins and supports registering plugin steps as asynchronous if they are long running (or don't need to be run in the synchronous pipeline). It would make more sense to use a synchronous HTTP call here like:
var response = httpClient.PostAsync(uri, content).Result;
EDIT: To illustrate, this is an overly-trivialized example of what is most likely happening when CRM goes to kickoff your plugin and you're using async/await (from LinqPad).
static async void CrmInternalFunctionThatKicksOffPlugins()
{
var plugin = new YourPlugin();
//NOTE Crm is not going to "await" your plugin execute method here
plugin.Execute();
"CRM is Done".Dump();
}
public class YourPlugin
{
public async void Execute()
{
await OnlineSignUp();
}
private async Task<HttpResponseMessage> OnlineSignUp()
{
var httpClient = new HttpClient();
var r = await httpClient.PostAsync("http://www.example.com", null);
"My Async Finished".Dump();
return r;
}
}
Which will print:
CRM is Done My Async Finished
looks like you are using Json.NET, when you use external assemblies there are some things to take care of (merging) and not always the result works.
Try to serialize using DataContractJsonSerializer
example: http://www.crmanswers.net/2015/02/json-and-crm-sandbox-plugins.html

Braintree test Request.Params always empty

I am implementing webhook test code and I am running into a problem.
After I POST a sample notification to my webhook listener the Params are not in the request:
Request.Params["bt_signature"]
Request.Params["bt_payload"]
So the listener fails.
Below is both my Post Webhook code and Listener code; I'm not sure if I'm using gateway.WebhookTesting.SampleNotification correctly.
POST Test Webhook
private async Task PostTestNotification()
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:50709/");
var gateway = config.GetGateway();
// Create sample notification
Dictionary<String, String> sampleNotification = gateway.WebhookTesting.SampleNotification(
WebhookKind.SUBSCRIPTION_CHARGED_SUCCESSFULLY, "sub_id_1234"
);
// Convert sample notification to JSON
string payloadJson = JsonConvert.SerializeObject(sampleNotification);
// Create StringContent of json sample notificaiton
var data = new StringContent(payloadJson);
// data looks like this when debugging { "bt_payload":"PG5vdGlmaWNhdGlvbj48dGltZXN0YW1wIHR5cGU9ImRhdGV0aW1lIj4yMDE2LTA1LTI3IDEzOjM2OjEwWjwvdGltZXN0YW1wPjxraW5kPnN1YnNjcmlwdGlvbl9jaGFyZ2VkX3N1Y2Nlc3NmdWxseTwva2luZD48c3ViamVjdD48c3Vic2NyaXB0aW9uPjxpZD5zdWJfaWRfMTIzNDwvaWQ+PHRyYW5zYWN0aW9ucz48dHJhbnNhY3Rpb24+PGlkPnN1Yl9pZF8xMjM0PC9pZD48YW1vdW50PjQ5Ljk5PC9hbW91bnQ+PHN0YXR1cz5zdWJtaXR0ZWRfZm9yX3NldHRsZW1lbnQ8L3N0YXR1cz48ZGlzYnVyc2VtZW50LWRldGFpbHM+PGRpc2J1cnNlbWVudC1kYXRlIHR5cGU9ImRhdGUiPjIwMTMtMDctMDk8L2Rpc2J1cnNlbWVudC1kYXRlPjwvZGlzYnVyc2VtZW50LWRldGFpbHM+PGJpbGxpbmc+PC9iaWxsaW5nPjxjcmVkaXQtY2FyZD48L2NyZWRpdC1jYXJkPjxjdXN0b21lcj48L2N1c3RvbWVyPjxkZXNjcmlwdG9yPjwvZGVzY3JpcHRvcj48c2hpcHBpbmc+PC9zaGlwcGluZz48c3Vic2NyaXB0aW9uPjwvc3Vic2NyaXB0aW9uPjwvdHJhbnNhY3Rpb24+PC90cmFuc2FjdGlvbnM+PGFkZF9vbnMgdHlwZT0iYXJyYXkiPjwvYWRkX29ucz48ZGlzY291bnRzIHR5cGU9ImFycmF5Ij48L2Rpc2NvdW50cz48L3N1YnNjcmlwdGlvbj48L3N1YmplY3Q+PC9ub3RpZmljYXRpb24+\n","bt_signature":"69r68j6hnzjpnq4j|508a7b4b3bbbe15c241c742331acfc5bacf37c54"}
// POST
HttpResponseMessage response = await client.PostAsync("webhooks/accept", data);
// RESPONSE
if (response.IsSuccessStatusCode == true)
{
// SUCCESS CONTENT
string resultJSON = await response.Content.ReadAsStringAsync();
}
else
{
// FAIL CONTENT
dynamic problem = await response.Content.ReadAsStringAsync();
}
}
}
catch (Exception ex)
{
//
Console.WriteLine("Exception: " + ex.Message);
}
}
Webhook LISTENER
// webhooks/accept endpoint
public async Task<ActionResult> accept()
{
try
{
var gateway = config.GetGateway();
if (Request.HttpMethod == "POST")
{
var bt_signature = Request.Params["bt_signature"]; <<<<<<< ALWAYS EMPTY >>>>>>>>>
var bt_payload = Request.Params["bt_payload"]; <<<<<<< ALWAYS EMPTY >>>>>>>>>
WebhookNotification webhookNotification = gateway.WebhookNotification.Parse(
Request.Params["bt_signature"],
Request.Params["bt_payload"]
); <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< EXCEPTION WHEN HIT - Value cannot be null, Parameter name: Input >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// ACTION Webhook
if (webhookNotification.Kind == WebhookKind.SUBSCRIPTION_CANCELED)
{
IsActive = false;
await Logger.LogInsight("", "WEBHOOK: SUBSCRIPTION_CANCELED " + webhookNotification.Subscription.Id );
}
else if (webhookNotification.Kind == WebhookKind.SUBSCRIPTION_CHARGED_SUCCESSFULLY)
{
IsActive = true;
await Logger.LogInsight("", "WEBHOOK: SUBSCRIPTION_CHARGED_SUCCESSFULLY " + webhookNotification.Subscription.Id);
}
// code ommitted for brevity, similar to above checking all 'kind' values
}
}
}
Why are the Braintree Request Params empty?
The problem was in the Webhook endpoint, the Braintree sample code is incorrect, it implies using MVC but developers need to use the Web Api for this, and the sample code will not work.
To get this working I left the Sample Notification POST above unchanged and created a new Webhook listener :
First create a class to receive the two braintree strings from the POST:
public class bt
{
public string bt_payload { get; set; }
public string bt_signature { get; set; }
}
And now create an empty Web Api 2 Controller:
[HttpPost]
[Route("api/webhooks/accept")]
public async Task<IHttpActionResult> accept(bt bt_lot)
{
var gateway = config.GetGateway();
WebhookNotification webhookNotification = gateway.WebhookNotification.Parse(
bt_lot.bt_signature,
bt_lot.bt_payload
);
if (webhookNotification.Kind == WebhookKind.SUBSCRIPTION_CANCELED)
{
// take your action here...
}
Im posting all my experiences with Braintree here on SO as there isn't a great deal of help here and I hope it helps others.
I have to say the Braintree Help staff are excellent and always answer questions with very detailed answers that 95% of the time resolved any issues I had, but this issue had me scratching my head as their example didn't work and the help staff assumed like me that the code should work.

Categories