I am trying to submit a BatchRequest and unsure of how to apply a filter or how to handle the callback.
class Program
{
static void Main(string[] args)
{
try
{
new Program().Run().Wait();
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
Console.Read();
}
private async Task Run()
{
var privatekey = "private key";
var accountEmailAddress = "email address";
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(accountEmailAddress) {
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromPrivateKey(privatekey));
var service = new AnalyticsService(new BaseClientService.Initializer() {
HttpClientInitializer = credentials,
ApplicationName = "Test"
});
var request = new BatchRequest(service);
request.Queue<DataResource.GaResource.GetRequest>(service.Data.Ga.Get("ga:XXXXXX", "30daysAgo", "yesterday", "ga:sessions"),
(content, error, i, message) =>
{
//callback code
});
await request.ExecuteAsync();
}
}
Two Questions:
How do I apply the following filter to the request?
ga:pagePath==/page1.html
How do I handle the callback and view the data returned?
Update
I believe I have solved the filter issue with the following code:
var req = service.Data.Ga.Get("ga:XXXXXX", "30daysAgo", "yesterday", "ga:sessions");
req.Filters = "ga:pagePath==/page1.html";
request.Queue<DataResource.GaResource.GetRequest>(req,
(content, error, i, message) =>
{
//callback code
});
I am still unsure of how to handle the callback. The "content" parameter is returned as the class: Google.Apis.Analytics.v3.DataResource.GaResource.GetRequest
I was finally able to get this working.
For future reference for anyone, here is a working .Net example of submitting a BatchRequest to the Analytics API.
class Program
{
static void Main(string[] args)
{
try
{
new Program().Run().Wait();
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
Console.Read();
}
private async Task Run()
{
var privatekey = "private key";
var accountEmailAddress = "email address";
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(accountEmailAddress)
{
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly }
}.FromPrivateKey(privatekey));
var service = new AnalyticsService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "Test"
});
var request = new BatchRequest(service);
BatchRequest.OnResponse<GaData> callback = (content, error, i, message) =>
{
if (error != null)
{
Console.WriteLine("Error: {0}", error.Message);
}
else
{
if (content.Rows != null)
{
foreach (var item in content.Rows)
{
foreach (var item1 in item)
{
Console.WriteLine(item1);
}
}
}
else
{
Console.WriteLine("Not Found");
}
}
};
int counter = 0;
while (counter < 5)
{
var req = service.Data.Ga.Get("ga:XXXXX", "30daysAgo", "yesterday", "ga:sessions");
req.Filters = "ga:pagePath==/page" + counter + ".html";
request.Queue<GaData>(req, callback);
counter++;
}
await request.ExecuteAsync();
}
}
So I am not familiar with C# but I did look at the source code for the get method. And the GetRequest object has a filters property which you can set.
var request = service.Data.Ga.Get("ga:XXXXXX",
"30daysAgo", "yesterday", "ga:sessions");
request.dimensions("ga:pagePath");
request.filters("ga:pagePath==/page1.html");
I would encourage you to also look at the Core Reporting API reference for the full set of parameters.
I would imagine that the content object in the callback method would correspond to the GaData response object
I hope that helps some.
Related
I'm working on an application based on .NET Framework 4.8. I'm using Microsoft Batching API. The below are code snippets
public async Task<List<BatchResponse>> UpdateEventsInBatchAsync(string accessToken, Dictionary<int, Tuple<string, OfficeEvent>> absEvents)
{
var httpMethod = new HttpMethod("PATCH");
var batches = GetUpdateRequestBatches(absEvents, httpMethod);
var graphClient = GetGraphClient(accessToken);
var batchResponses = new List<BatchResponse>();
foreach (var batch in batches)
{
try
{
var batchResponseList = await ExecuteBatchRequestAsync(graphClient, batch).ConfigureAwait(false);
batchResponses.AddRange(batchResponseList);
}
catch (ClientException exc)
{
_logService.LogException("Error while processing update batch", exc);
batchResponses.Add(new BatchResponse
{ StatusCode = HttpStatusCode.InternalServerError, ReasonPhrase = exc.Message });
}
catch (Exception exc)
{
_logService.LogException("Error while processing update batch", exc);
batchResponses.Add(new BatchResponse { StatusCode = HttpStatusCode.InternalServerError, ReasonPhrase = exc.Message });
}
}
return batchResponses;
}
The respective methods used in the above code are mentioned below in respective order-
GetUpdateRequestBatches
private IEnumerable<BatchRequestContent> GetUpdateRequestBatches(Dictionary<int, Tuple<string, OfficeEvent>> absEvents, HttpMethod httpMethod)
{
var batches = new List<BatchRequestContent>();
var batchRequestContent = new BatchRequestContent();
const int maxNoBatchItems = 20;
var batchItemsCount = 0;
foreach (var kvp in absEvents)
{
System.Diagnostics.Debug.Write($"{kvp.Key} --- ");
System.Diagnostics.Debug.WriteLine(_serializer.SerializeObject(kvp.Value.Item2));
var requestUri = $"{_msOfficeBaseApiUrl}/me/events/{kvp.Value.Item1}";
var httpRequestMessage = new HttpRequestMessage(httpMethod, requestUri)
{
Content = _serializer.SerializeAsJsonContent(kvp.Value.Item2)
};
var requestStep = new BatchRequestStep(kvp.Key.ToString(), httpRequestMessage);
batchRequestContent.AddBatchRequestStep(requestStep);
batchItemsCount++;
// Max number of 20 request per batch. So we need to send out multiple batches.
if (batchItemsCount > 0 && batchItemsCount % maxNoBatchItems == 0)
{
batches.Add(batchRequestContent);
batchRequestContent = new BatchRequestContent();
batchItemsCount = 0;
}
}
if (batchRequestContent.BatchRequestSteps.Count < maxNoBatchItems)
{
batches.Add(batchRequestContent);
}
if (batches.Count == 0)
{
batches.Add(batchRequestContent);
}
return batches;
}
GetGraphClient
private static GraphServiceClient GetGraphClient(string accessToken)
{
var graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(requestMessage =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
return graphClient;
}
ExecuteBatchRequestAsync
private async Task<List<BatchResponse>> ExecuteBatchRequestAsync(IBaseClient graphClient, BatchRequestContent batch)
{
BatchResponseContent response = await graphClient.Batch.Request().PostAsync(batch);
Dictionary<string, HttpResponseMessage> responses = await response.GetResponsesAsync();
var batchResponses = new List<BatchResponse>();
var failedReqKeys = new Dictionary<string, TimeSpan>();
foreach (var key in responses.Keys)
{
using (HttpResponseMessage httpResponseMsg = await response.GetResponseByIdAsync(key))
{
var responseContent = await httpResponseMsg.Content.ReadAsStringAsync();
string eventId = null;
var reasonPhrase = httpResponseMsg.ReasonPhrase;
if (!string.IsNullOrWhiteSpace(responseContent))
{
var eventResponse = JObject.Parse(responseContent);
eventId = (string)eventResponse["id"];
// If still null, then might error have occurred
if (eventId == null)
{
var errorResponse = _serializer.DeserializeObject<ErrorResponse>(responseContent);
var error = errorResponse?.Error;
if (error != null)
{
if (httpResponseMsg.StatusCode == (HttpStatusCode)429)
{
System.Diagnostics.Debug.WriteLine($"{httpResponseMsg.StatusCode} {httpResponseMsg.Content}");
var executionDelay = httpResponseMsg.Headers.RetryAfter.Delta ?? TimeSpan.FromSeconds(5);
failedReqKeys.Add(key, executionDelay);
continue;
}
reasonPhrase = $"{error.Code} - {error.Message}";
}
}
}
var batchResponse = new BatchResponse
{
Key = key,
EventId = eventId,
StatusCode = httpResponseMsg.StatusCode,
ReasonPhrase = reasonPhrase
};
batchResponses.Add(batchResponse);
}
}
if (failedReqKeys.Count == 0) return batchResponses;
return await HandleFailedRequestsAsync(graphClient, failedReqKeys, batch, batchResponses).ConfigureAwait(false);
}
HandleFailedRequestsAsync
private async Task<List<BatchResponse>> HandleFailedRequestsAsync(IBaseClient graphClient, Dictionary<string, TimeSpan> failedReqKeys, BatchRequestContent batch, List<BatchResponse> batchResponses)
{
// Sleep for the duration as suggested in RetryAfter
var sleepDuration = failedReqKeys.Values.Max();
Thread.Sleep(sleepDuration);
var failedBatchRequests = batch.BatchRequestSteps.Where(b => failedReqKeys.Keys.Contains(b.Key)).ToList();
var failedBatch = new BatchRequestContent();
foreach (var kvp in failedBatchRequests)
{
failedBatch.AddBatchRequestStep(kvp.Value);
}
var failedBatchResponses = await ExecuteBatchRequestAsync(graphClient, failedBatch);
batchResponses.AddRange(failedBatchResponses);
return batchResponses;
}
I'm getting an error as on the first line in method ExecuteBatchRequestAsync as
Microsoft.Graph.ClientException: Code: invalidRequest
Message: Unable to deserialize content.
---> System.ObjectDisposedException: Cannot access a closed Stream.
Can anyone nudge me where I'm doing wrong?
I must be missing something very obvious, but I can't tell what. I have a DoLoginAsync like so:
private async Task DoLoginAsync(bool force = false)
{
try
{
if (client.Cookies.ContainsKey("user_credentials") && !force)
{
return;
}
var html = client.Request("login").GetStringAsync().Result;
var doc = new HtmlDocument();
doc.LoadHtml(html);
var csrf_token = doc.DocumentNode.SelectNodes("//meta[#name='csrf-token']").First().GetAttributeValue("content", string.Empty);
var values = new Dictionary<string, string>
{
{ "user_session[email]", user },
{ "user_session[password]", password },
{ "authenticity_token", csrf_token }
};
var result = await client.Request("user_session").PostUrlEncodedAsync(values);
}
catch (Exception e)
{
}
When I run this code in a test with a breakpoint in the catch clause I get an exception
Call failed with status code 404 (Not Found): GET http://www.whatever.com/user_session
WTF? I'm expecting PostUrlEncodedAsync to do a POST, not a GET. Anybody have an idea why this can happen?
The Flurl client is instantiated as client = new FlurlClient(BASE_URL).EnableCookies();
UPDATE
Tried the following test which fails with the same exception
[TestMethod]
public async Task TheTest()
{
var message = "";
try
{
var client = new FlurlClient("http://www.slimmemeterportal.nl/").EnableCookies();
var html = await client.Request("login").GetStringAsync();
var doc = new HtmlDocument();
doc.LoadHtml(html);
var csrf_token = doc.DocumentNode.SelectNodes("//meta[#name='csrf-token']").First().GetAttributeValue("content", string.Empty);
var values = new Dictionary<string, string>
{
{ "user_session[email]", "******" },
{ "user_session[password]", "******" },
{ "commit", "inloggen" }, // Not sure if this is actually needed, but it is in the website's request parameters.
{ "authenticity_token", csrf_token }
};
var result = await client.Request("user_session").PostUrlEncodedAsync(values);
}
catch (FlurlHttpException ex)
{
message = ex.Message;
}
Assert.AreEqual("Call failed with status code 404 (Not Found): POST http://www.slimmemeterportal.nl/user_session", message);
}
Mystery solved: As it turns out after some debugging with Wireshark, the website was returning HTTP status code 301. As explained here the default action is to follow the URI in the response's location header using a GET even if the original request was a POST.
public ActionResult AuthorizeLinkedin(string code, string state, string error, string error_description)
{
//Linkedin
if (!string.IsNullOrEmpty(error) || !string.IsNullOrEmpty(error_description))
{
// handle error and error_description
}
else
{
objusermodel = new UserModel();
var host = ExtractHost();
var _redirectUrl = Url.Action("AuthorizeLinkedin", "Investments", null, Request.Url.Scheme, host);
var userToken = api.OAuth2.GetAccessToken(code, _redirectUrl);
// keep this token for your API requests
var user = new UserAuthorization(userToken.AccessToken);
var fields = FieldSelector.For().WithFirstName().WithFollowing();
if (Session["ShareResponse"] != null)
{
shareResponse = Session.GetObjectFromJson("ShareResponse");
}
//try
//{
var profile = api.Profiles.GetMyProfile(user, new string[] { "en-US" }, fields);
api.Social.Post(user, new PostShare()
{
Content = new PostShareContent()
{
Title = "FUNDING PORTAL",
Description = shareResponse.Message,
SubmittedUrl = shareResponse.ShareUrl,
SubmittedImageUrl = shareResponse.ImageUrl,
},
Visibility = new Visibility()
{
Code = "anyone"
},
});
shareResponse.Success = 1;
shareResponse.flag = true;
Session.SetObjectAsJson("ShareResponse", shareResponse);
TempData["PromoboxFN"] = shareResponse.ImageUrl;
TempData.Keep("PromoboxFN");
return Redirect(shareResponse.RedirectUrl);
//}
//catch (Exception ex)
//{
// throw;
// shareResponse.Success = 0;
// shareResponse.flag = true;
// Session.SetObjectAsJson("ShareResponse", shareResponse);
// return Redirect(shareResponse.RedirectUrl);
//}
}
return null;
}
Give the error:
"api.Social.Post(user, new PostShare()" to this point An exception of
type 'Sparkle.LinkedInNET.LinkedInApiException' occurred in
Sparkle.LinkedInNET.dll but was not handled in user code
Additional information: API error (500) Internal service error at https://api.linkedin.com/v1/people/~/shares
Yesterday I've found out how to create several async http requests without async/await. But today I need to do it in a loop: if some of responses don't satisfy some condition - I need to change a request for them and send these requests again. It may be repeated several times.
I've tried this code:
do
{
var loadingCoordinatesTasks = new List<Task<Terminal>>();
var totalCountOfTerminals = terminalPresetNode.ChildNodes.Count;
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
foreach (var terminal in terminals.Except(_terminalsWithCoordinates))
{
var address = terminal.GetNextAddress();
var webRequest = (HttpWebRequest)WebRequest.Create(GeoCoder.GeoCodeUrl + address);
var webRequestTask = Task.Factory.FromAsync<WebResponse>(webRequest.BeginGetResponse,
webRequest.EndGetResponse,
terminal);
var parsingTask = webRequestTask.ContinueWith(antecedent =>
{
// Parse the response
});
loadingCoordinatesTasks.Add(parsingTask);
}
Task.Factory.ContinueWhenAll(loadingCoordinatesTasks.ToArray(), antecedents =>
{
foreach (var antecedent in antecedents)
{
var terminalWithCoordinates = antecedent.Result;
if (antecedent.Status == TaskStatus.RanToCompletion &&
!terminalWithCoordinates.Coordinates.AreUnknown)
{
_terminalsWithCoordinates.Add(terminalWithCoordinates);
_countOfProcessedTerminals++;
}
}
});
} while (_countOfProcessedTerminals < totalCountOfTerminals);
but is it possible to check the condition in while just after every single set of requests executed?
You can perform the check after increasing the count:
_countOfProcessedTerminals++;
if (_countOfProcessedTerminals >= totalCountOfTerminals)
{
break;
}
Is _countOfProcessedTerminals thread-safe though?
I manage to do it using recursion:
public void RunAgainFailedTasks(IEnumerable<Task<Terminal>> tasks)
{
Task.Factory.ContinueWhenAll(tasks.ToArray(), antecedents =>
{
var failedTasks = new List<Task<Terminal>>();
foreach (var antecedent in antecedents)
{
var terminal = antecedent.Result;
// Previous request was failed
if (terminal.Coordinates.AreUnknown)
{
string address;
try
{
address = terminal.GetNextAddress();
}
catch (FormatException) // No versions more
{
continue;
}
var getCoordinatesTask = CreateGetCoordinatesTask(terminal, address);
failedTasks.Add(getCoordinatesTask);
}
else
{
_terminalsWithCoordinates.Add(terminal);
}
}
if (failedTasks.Any())
{
RunAgainFailedTasks(failedTasks);
}
else
{
// Display a map
}
}, CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
private Task<Terminal> CreateGetCoordinatesTask(Terminal terminal, string address)
{
var webRequest = (HttpWebRequest)WebRequest.Create(GeoCoder.GeoCodeUrl + address);
webRequest.KeepAlive = false;
webRequest.ProtocolVersion = HttpVersion.Version10;
var webRequestTask = Task.Factory.FromAsync<WebResponse>(webRequest.BeginGetResponse,
webRequest.EndGetResponse,
terminal);
var parsingTask = webRequestTask.ContinueWith(webReqTask =>
{
// Parse the response
});
return parsingTask;
}
I am new to Web api and json. I am unaware of calling method which are in WebApi. Below method is in Webapi:
[HttpGet]
public bool AddAccount([FromRoute]string accountname)
{
try
{
BizFramework.Web.Model.Account data = new BizFramework.Web.Model.Account();
data.AccountGuid = Guid.NewGuid();
data.AccountName = accountname;
data.ParentAccountID = 0;
data.AccountTypeID = 13;
data.AccountNo = "8060";
data.Active = true;
data.HierarchyLevel = 1;
data.CashFlowID = 3;
data.OpeningBalanceDate = DateTime.Now;
data.IscashBasis = true;
data.Createdby = "BAOwner";
data.CreatedDatetime = DateTime.Now;
data.Modifiedby = "BAOwner";
data.ModifiedDatetime = DateTime.Now;
BA.AddToAccounts(data);
BA.SaveChanges();
return true;
}
catch (Exception ex)
{
File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory + "log.txt", ex.ToString());
return false;
}
}
Using this link :
http://example.com/CustomerPortalService12/AddAccount/AccountsReceivable
I am able to add. But through the coding how can I add?
public void AddAccount(string accountname, Action<bool> success, Action<bool> failure)
{ var client = new RestClient("http://[localhost]/CustomerPortalService12");
// client.Authenticator = new HttpBasicAuthenticator(username, password);
var request = new RestRequest("AddAccount/AccountsReceivable/" + accountname, Method.GET);
client.ExecuteAsync(request, (response) =>
{
if (response.ResponseStatus == ResponseStatus.Error)
{
failure(response.ErrorMessage);
}
else
{
var result= JsonConvert.DeserializeObject<bool>(response.Content);
success(result);
}
}); }
to call this function
AddAccount("accountname",
(item) => Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("Done!");
}),
(error) => Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(error);
}));
I have done this by using RestSharp.
For more details go to here
http://restsharp.org/