Alexa Service Simulator Parse Error - c#

I am trying to add custom skill to Alexa Echo Dot using VS2015 and Alexa.Net nuget packages.
I am able to upload the lambda function to Amazon Webservice. And I am able to see the skills.
However when I try to test the lambda function using Service Simulator, I am getting the error below
"The remote endpoint could not be called, or the response it returned was invalid."
"Parse error on line 1:
The remote endpoint ^ Expecting 'STRING', NUMBER', NULL ... "
I am not sure what really missing here. Here is the details
Lambda Request
{
"session": {
"sessionId": "SessionId.23409e06-265b-4704-a288-8d5329a68a68",
"application": {
"applicationId": "amzn1.ask.skill.55a9cca9-02dc-4780-a55c-c1d0dee6b8c6"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.AHPIWHCHA22Z3WAJGS2ABA3MQ3PTKB4HOMJIBBDILIBPWTSAAOELN45D4PIV3U75IOBDHNGJQ36OSUYK43VQKYSQFIM2OHHOORSDWM2HMLWKINLCLKU7R3SNONWM7YPWSMR5XGN6XKVZGBG4NFHDQXACZLVK57MXUOIYYV6RLLVACBMMSFPVDINMO3QKQUZVZMVR73KTCEYTCRY"
},
"new": true
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.082b6e56-29d4-4eed-a353-e24890cfbefa",
"locale": "en-US",
"timestamp": "2017-07-11T12:19:27Z",
"intent": {
"name": "CountryInfoIntent",
"slots": {
"Country": {
"name": "Country",
"value": "France"
}
}
}
},
"version": "1.0"
}
Function Handler
public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
{
var requestType = input.GetRequestType();
if (requestType == typeof(IntentRequest))
{
return MakeSkillResponse(
$"Hello Infotec! This is the first response from your Alexa skill using c sharp.",
true);
}
else
{
return MakeSkillResponse(
$"I don't know how to handle this intent. Please say something like Alexa, ask {INVOCATION_NAME} about Canada.",
true);
}
}
private SkillResponse MakeSkillResponse(string outputSpeech, bool shouldEndSession, string repromptText = "Just say, tell me about Canada to learn more. To exit, say, exit.")
{
var response = new ResponseBody
{
ShouldEndSession = shouldEndSession,
OutputSpeech = new PlainTextOutputSpeech { Text = outputSpeech }
};
if (repromptText != null)
{
response.Reprompt = new Reprompt() { OutputSpeech = new PlainTextOutputSpeech() { Text = repromptText } };
}
var skillResponse = new SkillResponse
{
Response = response,
Version = "1.0"
};
return skillResponse;
}

It was a bug in the Alexa.Net nuget package. And that has been fixed in the latest package.
https://github.com/timheuer/alexa-skills-dotnet/commit/5c6dc0d2c0e3e16ca055d8938b1d0f24ad9670ed

Related

Error while creating a WorkItem via API - Devops

I'm trying to create a workitem via API, but im getting the following error:
{
"innerException": null,
"message": "You must pass a valid patch document in the body of the request.",
"typeName": "Microsoft.VisualStudio.Services.Common.VssPropertyValidationException, Microsoft.VisualStudio.Services.Common",
"typeKey": "VssPropertyValidationException",
"errorCode": 0,
"eventId": 3000
}
Code:
public class Chamados
{
public async Task<string> CriaChamadoDevOps()
{
string organizacao = "xxx";
string departamento = "xxx";
string tipoWorkItem = "xxx";
string authToken = "xxx";
// Montando a Requisição
string urlReq = "https://dev.azure.com/" + organizacao + "/" + departamento + "/_apis/wit/workitems/$" + tipoWorkItem + "?api-version=6.0";
var client = new RestClient(urlReq);
var request = new RestRequest(urlReq, Method.Post);
// Montando Headers
request.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", authToken))));
request.AddHeader("Content-Type", "application/json-patch+json; charset=utf-8");
var body = new JArray {
new JObject {
{ "op", "add" },
{ "path", "/fields/System.Title" },
{ "value", "Assunto Teste" }
},
new JObject {
{ "op", "add" },
{ "path", "/fields/System.State" },
{ "value", "To do" }
},
new JObject {
{ "op", "add" },
{ "path", "/fields/System.Description" },
{ "value", "Descricao Teste" }
},
};
//request.AddBody(body);
request.AddParameter("application/json-patch+json; charset=utf-8", body, ParameterType.RequestBody);
Console.WriteLine(body);
RestResponse response = await client.ExecuteAsync(request);
dynamic resposta = JsonConvert.DeserializeObject(response.Content);
return resposta.ToString();
}
}
When i test it via Postman, it works.
This is how im sending the body to the request:
(Output from Console.WriteLine(body);)
[
{
"op": "add",
"path": "/fields/System.Title",
"value": "Assunto Teste"
},
{
"op": "add",
"path": "/fields/System.State",
"value": "To do"
},
{
"op": "add",
"path": "/fields/System.Description",
"value": "Descricao Teste"
}
]
And i've also tried replacing the "request.AddParameter()" with "request.AddBody()" method.
Maybe you can start by reading the docs: https://restsharp.dev/usage.html#request-body
Then, you'd figure out that since you don't want to build strongly-typed request models and prefer using JObject, you need to handle the serialization yourself:
request.AddStringBody(body.ToString(), "application/json-patch+json");
and that the docs tell you not to add content-type as the request header as it's the content header, and AddStringBody will do it.

C# format an array for post to Api

I am trying to get the correct format for the json array I need to pass to a post request.
The example code I got shows the following format when using c# as the preferred programming language:
var client = new RestClient("https://api.us/v2/webinars/94395753143/panelists");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddHeader("authorization", "Bearer _XU6l1eaDs9NQRTcb5QG4m0-ab1F3Y29ikw");
request.AddParameter("application/json", "{\"panelists\":[{\"name\":\"Mary\",\"email\":\"maryjkdfdsgfshdgf#jdfdkjdglfk.jkfgdj\"},{\"name\":\"Mike\",\"email\":\"dfdsgfsdhf#jkgfdgfkdhgfdjg.fkjgdf\"}]}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
As proof of concept, I am getting the values from an angularjs view formatting the json array, and passing them through the post request:
$http(request)
.then(function successCallback(data) {
angular.forEach(data.data, function (item) {
var b = {
name: item.Name,
email: item.Email
};
$scope.arr.push(b);
});
var parData = JSON.stringify({ 'panelists': $scope.arr, 'id': $scope.webinarId, 'bearer': $scope.bearer});
$http.post('/api/AddPanelists', parData)
.then(function (data) {
}), function (data) {
alert("An error occurred during the request");
};
In my C# code I have a function that receives parDAta and retrieves each of the 3 elements passed (array, id, bearer)
[HttpPost]
public void CreatePanelists(Newtonsoft.Json.Linq.JObject data)
{
Parameters parameters = JsonConvert.DeserializeObject<Parameters>(data.ToString());
IList<Panelist> panelists = parameters.panelists; << this value does not seem right
string webId = parameters.id; << this value is fine
string bearer = parameters.bearer; << this value is fine
var client = new RestSharp.RestClient("https://api.us/v2/webinars/" + webId + "/panelists");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json;charset=UTF-8");
request.AddHeader("authorization", "Bearer " + bearer);
request.AddParameter("application/json", panelists, ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
var content = response.Content;
}
public class Panelist
{
public string name { get; set; }
public string email { get; set; }
}
public class Parameters
{
public IList<Panelist> panelists { get; set; }
public string id { get; set; }
public string bearer { get; set; }
}
When I check the console.log in the browser, to see if JavaScript is formatting the json is correct, I see this:
{"panelists":[{"name":"Jack Anderson","email":"janderson#email.com"},{"name":"Ed Johnson","email":"ejohnson#email.com"},{"name":"Dead Poole","email":"dpoole#email.com"},{"name":"Hank Schmidt","email":"hschmidt#email.com"},{"name":"Steven Alves","email":"salves#email.com"},{"name":"Nilvio Alexander","email":"nalexanderemail.com"}],"id":94395753143,"bearer":"U19hW2pkQkO2A0Zv5EXz-h4kXJ56s"}
When I check the value once it makes it to the C# side of things, the string looks a bit different:
When I check what has been passed, here:
public void CreatePanelists(Newtonsoft.Json.Linq.JObject data) << here
The value looks:
{{
"panelists": [
{
"name": "Jack Anderson",
"email": "janderson#email.com"
},
{
"name": "Ed Johnson",
"email": "ejohnson#email.com"
},
{
"name": "Dead Poole",
"email": "dpoole#email.com"
},
{
"name": "Hank Schmidt",
"email": "hschmidt#email.com"
},
{
"name": "Steven Alves",
"email": "salves#email.com"
},
{
"name": "Nilvio Alexander",
"email": "nalexander#email.com"
}
],
"id": 94395753143,
"bearer": "U19hW2pkQkO2A0Zv5EXz-h4kXJ56s"
}}
I see extra curly brackets at the beginning and at the end of the string.
My goal is to be able to pass an array that looks exactly as the 3rd party api needs the array to be formatted, I do not think I am doing it correctly.
This is the format I need to have, specifically for the panelist string
{
"panelists": [
{
"name": "Jack Anderson",
"email": "janderson#email.com"
},
{
"name": "Ed Johnson",
"email": "ejohnson#email.com"
},
{
"name": "Dead Poole",
"email": "dpoole#email.com"
},
{
"name": "Hank Schmidt",
"email": "hschmidt#email.com"
},
{
"name": "Steven Alves",
"email": "salves#email.com"
},
{
"name": "Nilvio Alexander",
"email": "nalexander#email.com"
}
]
}
Thank you for reading my question, I hope I can get a bit of help.
If I need to clarify anything, please just let me know.
Thanks,
Erasmo
UPDATE
I think the extra curly braces are gone, but how do I know that the string to be passed will look like this:
{
"panelists": [
{
"name": "Jack Anderson",
"email": "janderson#email.com"
},
{
"name": "Ed Johnson",
"email": "ejohnson#email.com"
},
{
"name": "Dead Poole",
"email": "dpoole#email.com"
},
{
"name": "Hank Schmidt",
"email": "hschmidt#email.com"
},
{
"name": "Steven Alves",
"email": "salves#email.com"
},
{
"name": "Alejandro Nava-Gomez",
"email": "anava-gomez#cityofmadison.com"
},
{
"name": "itortu",
"email": "itortu#gmail.com"
}
]
}
If you see the screen shot, I am not sure it looks correct. May be I am looking at the output and not understanding what the actual value will be passed.
You can create new object to satisfy expected structure.
[HttpPost]
public void CreatePanelists([FromBody] Parameters parameters)
{
var webId = parameters.id;
var bearer = parameters.bearer;
var data = new
{
panelists = parameters.panelists
};
var client =
new RestSharp.RestClient($"https://api.us/v2/webinars/{webId}/panelists");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json;charset=UTF-8");
request.AddHeader("authorization", $"Bearer {bearer}");
request.AddParameter("application/json", data, ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
var content = response.Content;
}

Getting declined (2046: Transaction Refused) from Paypal Sandbox transaction

I'm using Braintree SDK in .NET. Implemented the SDK and followed the documentation.
Created Sandbox Merchant Account
Created Sandbox Personal Account
I'm using the Access Token for the transaction. Please refer the following code snippet:
//--------------------------- Get Token for client ---------------------------
public PaypalHelper.Paypal_ResponseObj<string> GetPaypalClientToken()
{
string PaypalAccessToken = "XXXXXXXXXXXXXXXXXXX";
if (PaypalAccessToken != null)
{
BraintreeGateway gateway = new BraintreeGateway(PaypalAccessToken);
string clientToken = gateway.ClientToken.Generate();
return new PaypalHelper.Paypal_ResponseObj<string>()
{
Code = HttpStatusCode.OK,
Message = "Payment from paypal successfully initiated.",
Response = clientToken
};
}
else
{
return new PaypalHelper.Paypal_ResponseObj<string>()
{
Code = HttpStatusCode.Unauthorized,
Message = "Unauthorized! Paypal access token is not set or invalid.",
Response = null
};
}
}
//--------------------------- Do Paypal transaction ---------------------------
public PaypalHelper.Paypal_ResponseObj<Result<Transaction>> PayPalTransaction(string Nonce, decimal Amount)
{
if (!string.IsNullOrEmpty(Nonce) && !string.IsNullOrWhiteSpace(Nonce) && Amount > 0)
{
string PaypalAccessToken = "XXXXXXXXXXXXXXXXXXX";
if (PaypalAccessToken != null)
{
BraintreeGateway gateway = new BraintreeGateway(PaypalAccessToken);
var request = new TransactionRequest
{
Amount = Amount,
PaymentMethodNonce = Nonce,
Options = new TransactionOptionsRequest
{
SubmitForSettlement = true
}
};
var result = gateway.Transaction.Sale(request);
if (result.IsSuccess())
{
if (result.Errors == null && result.Target != null)
{
return new PaypalHelper.Paypal_ResponseObj<Result<Transaction>>()
{
Code = HttpStatusCode.OK,
Message = "Payment from paypal successfully initiated.",
Response = result
};
}
else
{
return new PaypalHelper.Paypal_ResponseObj<Result<Transaction>>()
{
Code = HttpStatusCode.Conflict,
Message = "Unable to process this request.",
Response = result
};
}
}
else
{
return new PaypalHelper.Paypal_ResponseObj<Result<Transaction>>()
{
Code = HttpStatusCode.Conflict,
Message = "Payment unsuccessfull.",
Response = result
};
}
}
else
{
return new PaypalHelper.Paypal_ResponseObj<Result<Transaction>>()
{
Code = HttpStatusCode.Unauthorized,
Message = "Un authorized! Paypal access token is not set or invalid.",
Response = null
};
}
}
else {
return new PaypalHelper.Paypal_ResponseObj<Result<Transaction>>()
{
Code = HttpStatusCode.BadRequest,
Message = "input can't be null!",
Response = null
};
}
}
Just to summarize all, we have a PayPal business account and we also created a Sandbox business merchant account and Sandbox personal account to test this scenario.
Please refer the following payload being sent to PayPal for the transaction:
{
"CreditCard":null,
"Amount":235.0000,
"DeviceData":null,
"DeviceSessionId":null,
"FraudMerchantId":null,
"Channel":null,
"OrderId":"SWS-O-RQ-894XX",
"Recurring":null,
"TransactionSource":null,
"MerchantAccountId":"USD",
"PurchaseOrderNumber":null,
"Customer":{
"Id":null,
"DeviceData":null,
"CustomerId":null,
"FirstName":"John",
"LastName":"Doe",
"Company":null,
"Email":"johndoe#swsn.com",
"Phone":"330459218319",
"Fax":null,
"Website":null,
"PaymentMethodNonce":null,
"DefaultPaymentMethodToken":null,
"CustomFields":null,
"CreditCard":null,
"UsBankAccount":null,
"RiskData":null,
"Options":null
},
"Descriptor":null,
"Industry":null,
"BillingAddress":null,
"ShippingAddress":{
"FirstName":"John",
"LastName":"Doe",
"Company":null,
"StreetAddress":"XX Boulevard Haussmann",
"ExtendedAddress":null,
"Locality":"Paris",
"Region":null,
"PostalCode":"75008",
"CountryCodeAlpha2":"FR",
"CountryCodeAlpha3":null,
"CountryCodeNumeric":null,
"CountryName":null
},
"PayPalAccount":null,
"TaxAmount":null,
"TaxExempt":null,
"Type":null,
"CustomFields":{
},
"Options":{
"HoldInEscrow":null,
"StoreInVault":null,
"StoreInVaultOnSuccess":null,
"AddBillingAddressToPaymentMethod":null,
"StoreShippingAddressInVault":null,
"SubmitForSettlement":true,
"VenmoSdkSession":null,
"PayeeId":null,
"PayeeEmail":null,
"SkipAdvancedFraudChecking":null,
"SkipAvs":null,
"SkipCvv":null,
"PayPal":null,
"ThreeDSecure":null,
"AmexRewards":null,
"Venmo":null,
"CreditCard":null
},
"ThreeDSecurePassThru":null,
"PaymentMethodToken":null,
"CustomerId":null,
"ShippingAddressId":null,
"BillingAddressId":null,
"VenmoSdkPaymentMethodCode":null,
"PaymentMethodNonce":"799178b2-1e00-0ac7-6db0-034a9a668b1a",
"ServiceFeeAmount":null,
"SharedPaymentMethodToken":null,
"SharedPaymentMethodNonce":null,
"SharedCustomerId":null,
"SharedShippingAddressId":null,
"SharedBillingAddressId":null,
"ThreeDSecureToken":null,
"RiskData":null,
"DiscountAmount":null,
"ShippingAmount":null,
"ShipsFromPostalCode":null,
"LineItems":null,
"ExternalVault":null
}
Please refer the following response object that we are getting from PayPal after initiating a transaction:
{
"CreditCardVerification":null,
"Transaction":{
"Id":"r9b19cwh",
"AddOns":[
],
"Amount":235,
"AvsErrorResponseCode":null,
"AvsPostalCodeResponseCode":"I",
"AvsStreetAddressResponseCode":"I",
"BillingAddress":{
"Id":null,
"CustomerId":null,
"FirstName":null,
"LastName":null,
"Company":null,
"StreetAddress":null,
"ExtendedAddress":null,
"Locality":null,
"Region":null,
"PostalCode":null,
"CountryCodeAlpha2":null,
"CountryCodeAlpha3":null,
"CountryCodeNumeric":null,
"CountryName":null,
"CreatedAt":null,
"UpdatedAt":null
},
"Channel":null,
"CreatedAt":"2020-03-31T19:25:43Z",
"CreditCard":{
"Bin":null,
"CardholderName":null,
"CardType":{
},
"CreatedAt":null,
"CustomerId":null,
"IsDefault":null,
"IsVenmoSdk":false,
"IsExpired":null,
"CustomerLocation":{
},
"LastFour":null,
"UniqueNumberIdentifier":null,
"Subscriptions":[
],
"Token":null,
"UpdatedAt":null,
"BillingAddress":{
"Id":null,
"CustomerId":null,
"FirstName":null,
"LastName":null,
"Company":null,
"StreetAddress":null,
"ExtendedAddress":null,
"Locality":null,
"Region":null,
"PostalCode":null,
"CountryCodeAlpha2":null,
"CountryCodeAlpha3":null,
"CountryCodeNumeric":null,
"CountryName":null,
"CreatedAt":null,
"UpdatedAt":null
},
"ExpirationMonth":null,
"ExpirationYear":null,
"Prepaid":{
},
"Payroll":{
},
"Debit":{
},
"Commercial":{
},
"Healthcare":{
},
"DurbinRegulated":{
},
"ImageUrl":"https://assets.braintreegateway.com/payment_method_logo/unknown.png?environment=sandbox",
"Verification":null,
"AccountType":null,
"CountryOfIssuance":"Unknown",
"IssuingBank":"Unknown",
"ProductId":"Unknown",
"ExpirationDate":"/",
"MaskedNumber":"**"
},
"CurrencyIsoCode":"USD",
"CustomerDetails":{
"Id":null,
"FirstName":"John",
"LastName":"Doe",
"Company":null,
"Email":"johndoe#swsln.com",
"Phone":"000459210000",
"Fax":null,
"Website":null
},
"CvvResponseCode":"I",
"Descriptor":{
"Name":null,
"Phone":null,
"Url":null
},
"Discounts":[
],
"Disputes":[
],
"GatewayRejectionReason":{
},
"MerchantAccountId":"USD",
"OrderId":"SWS-O-RQ-894XX",
"PlanId":null,
"ProcessorAuthorizationCode":null,
"ProcessorResponseType":{
},
"ProcessorResponseCode":"2046",
"ProcessorResponseText":"Declined",
"ProcessorSettlementResponseCode":null,
"ProcessorSettlementResponseText":null,
"AdditionalProcessorResponse":"2046 : TRANSACTION_REFUSED",
"VoiceReferralNumber":null,
"PurchaseOrderNumber":null,
"Recurring":false,
"RefundedTransactionId":null,
"RefundIds":[
],
"PartialSettlementTransactionIds":[
],
"AuthorizedTransactionId":null,
"SettlementBatchId":null,
"ShippingAddress":{
"Id":null,
"CustomerId":null,
"FirstName":"John",
"LastName":"Doe",
"Company":null,
"StreetAddress":"XX Boulevard XXXX",
"ExtendedAddress":null,
"Locality":"Paris",
"Region":null,
"PostalCode":"75008",
"CountryCodeAlpha2":"FR",
"CountryCodeAlpha3":"FRA",
"CountryCodeNumeric":"250",
"CountryName":"France",
"CreatedAt":null,
"UpdatedAt":null
},
"EscrowStatus":{
},
"Status":{
},
"StatusHistory":[
{
"Amount":235,
"Status":{
},
"Timestamp":"2020-03-31T19:25:48Z",
"Source":{
},
"User":null
}
],
"AuthorizationAdjustments":[
],
"SubscriptionId":null,
"SubscriptionDetails":{
"BillingPeriodEndDate":null,
"BillingPeriodStartDate":null
},
"TaxAmount":null,
"TaxExempt":false,
"Type":{
},
"UpdatedAt":"2020-03-31T19:25:48Z",
"CustomFields":{
},
"ServiceFeeAmount":null,
"DisbursementDetails":{
"SettlementAmount":null,
"SettlementCurrencyIsoCode":null,
"SettlementCurrencyExchangeRate":null,
"FundsHeld":null,
"Success":null,
"DisbursementDate":null
},
"ApplePayDetails":null,
"AndroidPayDetails":null,
"AmexExpressCheckoutDetails":null,
"PayPalDetails":{
"PayerEmail":"sb-zkd0t1338940#personal.example.com",
"PaymentId":null,
"AuthorizationId":null,
"Token":null,
"ImageUrl":"https://assets.braintreegateway.com/payment_method_logo/paypal.png?environment=sandbox",
"DebugId":"20ac958b09914",
"PayeeId":null,
"PayeeEmail":null,
"CustomField":null,
"PayerId":null,
"PayerFirstName":null,
"PayerLastName":null,
"PayerStatus":null,
"SellerProtectionStatus":null,
"CaptureId":null,
"RefundId":null,
"TransactionFeeAmount":null,
"TransactionFeeCurrencyIsoCode":null,
"RefundFromTransactionFeeAmount":null,
"RefundFromTransactionFeeCurrencyIsoCode":null,
"Description":null
},
"LocalPaymentDetails":null,
"CoinbaseDetails":null,
"VenmoAccountDetails":null,
"UsBankAccountDetails":null,
"IdealPaymentDetails":null,
"VisaCheckoutCardDetails":null,
"MasterpassCardDetails":null,
"SamsungPayCardDetails":null,
"PaymentInstrumentType":{
},
"RiskData":null,
"ThreeDSecureInfo":null,
"FacilitatedDetails":null,
"FacilitatorDetails":null,
"DiscountAmount":null,
"ShippingAmount":null,
"ShipsFromPostalCode":null,
"NetworkTransactionId":null,
"AuthorizationExpiresAt":null
},
"Subscription":null,
"Errors":{
"Count":0,
"DeepCount":0
},
"Parameters":{
"transaction[amount]":"235.00",
"transaction[order_id]":"SWS-O-RQ-894XX",
"transaction[payment_method_nonce]":"799178b2-1e00-0ac7-6db0-034a9a668bXX",
"transaction[merchant_account_id]":"USD",
"transaction[type]":"sale",
"transaction[customer][first_name]":"John",
"transaction[customer][last_name]":"Doe",
"transaction[customer][email]":"johndoe#swsln.com",
"transaction[customer][phone]":"330459218319",
"transaction[shipping][first_name]":"John",
"transaction[shipping][last_name]":"Doe",
"transaction[shipping][street_address]":"63 Boulevard Haussmann",
"transaction[shipping][locality]":"Paris",
"transaction[shipping][postal_code]":"75008",
"transaction[shipping][country_code_alpha2]":"FR",
"transaction[options][submit_for_settlement]":"true"
},
"Message":"Declined",
"Target":null
}
Anyone? who can help me to figure out this issue. Is there something I'm missing?

Converting this code to use Microsoft Graph with batch mode

Here is my code for deleting a set of calendar entries:
public async Task<bool> DeleteCalendarEvents(SettingsBase oSettings)
{
try
{
var oEvents = await _graphClient
.Me
.Calendars[oSettings.CalendarID]
.Events
.Request()
.Select("Start,Subject,Id")
.Top(50)
.Filter(oSettings.GetFilterString())
.OrderBy("start/DateTime")
.GetAsync();
List<Event> listEvents = new List<Event>();
listEvents.AddRange(oEvents);
while (oEvents.NextPageRequest != null)
{
oEvents = await oEvents.NextPageRequest.GetAsync();
listEvents.AddRange(oEvents);
}
foreach (Event oEvent in listEvents)
{
await _graphClient.Me.Events[oEvent.Id].Request().DeleteAsync();
}
}
catch (Exception ex)
{
SimpleLog.Log(ex);
Console.WriteLine("DeleteCalendarEvents: See error log.");
return false;
}
return true;
}
I then have a method that adds new events into the calendar:
public async Task<bool> AddEventsToCalendar(MWBData.MWBCalendarData oData)
{
if (oData.SettingsMWB.CalendarEntryType != "CLM_MidweekMeeting")
{
SimpleLog.Log("AddEventsToCalendar: CalendarEntryType is not set to CLM_MidweekMeeting.", SimpleLog.Severity.Error);
Console.WriteLine("AddEventsToCalendar: See error log.");
return false;
}
try
{
// Now create the new events
foreach (EventWeek oWeek in oData.Weeks)
{
bool bSuccess = await AddEventToCalendar(oWeek, oData.SettingsMWB);
if(bSuccess)
{
// Now create any Weekend Meeting events
if(oWeek.WeekendMeeting.Included)
{
bSuccess = await AddEventToCalendar(oWeek.WeekendMeeting, oData.SettingsMWB);
if(!bSuccess)
{
Console.WriteLine("AddEventsToCalendar: See error log.");
return false;
}
}
}
else
{
Console.WriteLine("AddEventToCalendar: See error log.");
return false;
}
}
}
catch (Exception ex)
{
SimpleLog.Log(ex);
Console.WriteLine("AddEventsToCalendar: See error log.");
return false;
}
return true;
}
As you can see, for each event it calls AddEventToCalendar. That method, in part, creates the event like this:
// Add the event
Event createdEvent = await _graphClient.Me.Calendars[oSettings.CalendarID].Events.Request().AddAsync(new Event
{
Subject = oEvent.GetSubject(),
Body = body,
Start = startTime,
End = endTime,
IsAllDay = oEvent.IsAllDayEvent(),
IsReminderOn = bSetReminder,
ReminderMinutesBeforeStart = bSetReminder ? iReminderMinutes : (int?)null,
Location = location,
SingleValueExtendedProperties = extendedProperties,
Sensitivity = oSettings.SetCalendarPrivate ? Sensitivity.Private : Sensitivity.Normal
});
Now, I know that Microsoft Graph supports batch mode using JSON. But I am at a loss as to how to implement that with what I have written. It makes sense to try and convert my code into a list of batch operations to reduce the calls.
How do I do this?
Update
I have located this article but I am not sure if it is relevant and what I should do. So I would still appreciate any specific guidance with how to do this. I am sure that other potential users would benefit from this greatly - or be directed to an existing resource that I have missed. Thank you.
Even though batch request is not yet supported you could consider the following proof-of-concept that demonstrates how to implement a basic support for retrieving entities on top of msgraph-sdk-dotnet:
//1.Construct a Batch request
var batchRequest = new BatchRequest();
//2. Add sub queries. Two parameters needs to be specified: a request and a result object
batchRequest.AddQuery(graphClient.Users[loginName].Request(), typeof(Microsoft.Graph.User));
batchRequest.AddQuery(graphClient.Sites["root"].Request(),typeof(Microsoft.Graph.Site));
//3. Execute Batch request
var result = await graphClient.GetBatchAsync(batchRequest);
where BatchRequest class introduces a support for Batch request
public class BatchRequest
{
private Dictionary<string, IBaseRequest> _queriesTable = new Dictionary<string, IBaseRequest>();
private Dictionary<string,Type> _resultsTable = new Dictionary<string, Type>();
public KeyValuePair<IBaseRequest, Type> this[string queryId]
{
get
{
return new KeyValuePair<IBaseRequest,Type>(_queriesTable[queryId], _resultsTable[queryId]);
}
}
public void AddQuery(IBaseRequest query, Type entityType)
{
var queryId = Guid.NewGuid().ToString();
_resultsTable[queryId] = entityType;
_queriesTable[queryId] = query;
}
/// <summary>
/// Construct JSON batch request https://developer.microsoft.com/en-us/graph/docs/concepts/json_batching
/// </summary>
/// <param name="client"></param>
/// <returns></returns>
public HttpRequestMessage ToMessage(GraphServiceClient client)
{
var batchMessage = new HttpRequestMessage();
batchMessage.RequestUri = new Uri("https://graph.microsoft.com/v1.0/$batch");
batchMessage.Method = HttpMethod.Post;
batchMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
dynamic payload = new ExpandoObject();
payload.requests = _queriesTable.Select(kv =>
{
var message = kv.Value.GetHttpRequestMessage();
dynamic request = new ExpandoObject();
request.id = kv.Key;
request.method = message.Method.ToString();
request.url = message.RequestUri.AbsoluteUri.Replace(client.BaseUrl,string.Empty);
if(message.Content != null)
request.body = message.Content;
request.headers = message.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault());
return request;
});
var jsonPayload = client.HttpProvider.Serializer.SerializeObject(payload);
batchMessage.Content = new StringContent(jsonPayload,Encoding.UTF8,"application/json");
return batchMessage;
}
}
and GetBatchAsync is extension method to execute a batch request:
public static class GraphServiceClientExtensions
{
public static async Task<List<object>> GetBatchAsync(this GraphServiceClient client, BatchRequest request)
{
var batchMessage = request.ToMessage(client);
await client.AuthenticationProvider.AuthenticateRequestAsync(batchMessage);
var response = await client.HttpProvider.SendAsync(batchMessage);
var content = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(content);
var entities = json["responses"].Select(item =>
{
var queryId = (string)item["id"];
var entityPayload = JsonConvert.SerializeObject(item["body"]);
var subRequest = request[queryId];
var entity = JsonConvert.DeserializeObject(entityPayload, subRequest.Value);
return entity;
});
return entities.ToList();
}
}
Gist: Microsoft.Graph.BatchSupport.cs
Like i said it is just a proof-of-concept and only retrieving of entities are supported in this implementation, so if you want to implement another operations like delete you could utilize it as a starting point
Results
This is not supported in the graph library now.
https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/136
A workaround for you, use HttpClient to request batch endpoint,
Mock jsonObject(You need to use StringBuilder or such to create the string, use for/foreach to iterate over the event list and then append item info to the json object, just remember to increment the id value):
Note: Nested json seems doesn't work on Graph, so this workground still need test.
{
"requests": [
{
"id": "1",
"url": "/me",
"method": "POST",
"body": {
"subject": "Let's go for lunch",
"body": {
"contentType": "HTML",
"content": "Does late morning work for you?"
},
"start": {
"dateTime": "2017-04-15T12:00:00",
"timeZone": "Pacific Standard Time"
},
"end": {
"dateTime": "2017-04-15T14:00:00",
"timeZone": "Pacific Standard Time"
},
"location":{
"displayName":"Harry's Bar"
},
"attendees": [
{
"emailAddress": {
"address":"samanthab#contoso.onmicrosoft.com",
"name": "Samantha Booth"
},
"type": "required"
}
]
},
"headers": {
"Content-Type": "application/json"
}
},
{
"id": "2",
"url": "/me",
"method": "POST",
"body": {
"subject": "Let's go for lunch",
"body": {
"contentType": "HTML",
"content": "Does late morning work for you?"
},
"start": {
"dateTime": "2017-04-15T12:00:00",
"timeZone": "Pacific Standard Time"
},
"end": {
"dateTime": "2017-04-15T14:00:00",
"timeZone": "Pacific Standard Time"
},
"location":{
"displayName":"Harry's Bar"
},
"attendees": [
{
"emailAddress": {
"address":"samanthab#contoso.onmicrosoft.com",
"name": "Samantha Booth"
},
"type": "required"
}
]
},
"headers": {
"Content-Type": "application/json"
}
},
{
"id": "3",
"url": "/me",
"method": "POST",
"body": {
"subject": "Let's go for lunch",
"body": {
"contentType": "HTML",
"content": "Does late morning work for you?"
},
"start": {
"dateTime": "2017-04-15T12:00:00",
"timeZone": "Pacific Standard Time"
},
"end": {
"dateTime": "2017-04-15T14:00:00",
"timeZone": "Pacific Standard Time"
},
"location":{
"displayName":"Harry's Bar"
},
"attendees": [
{
"emailAddress": {
"address":"samanthab#contoso.onmicrosoft.com",
"name": "Samantha Booth"
},
"type": "required"
}
]
},
"headers": {
"Content-Type": "application/json"
}
}
]
}
Execute code:
var content = new StringContent(jsonObject.ToString(), Encoding.UTF8, "application/json");
var result = await client.PostAsync(url, content);

How to commit a change to a file using the GitHub DB API

I am trying to update a file using the GitHub DB API using c#. The process for doing this is defined here http://developer.github.com/v3/git/ and is as follows
get the current commit object
retrieve the tree it points to
retrieve the content of the blob object that tree has for that particular file path
change the content somehow and post a new blob object with that new content, getting a blob SHA back
post a new tree object with that file path pointer replaced with your new blob SHA * getting a tree SHA back
create a new commit object with the current commit SHA as the parent and the new tree SHA, getting a commit SHA back
update the reference of your branch to point to the new commit SHA
However it fails when I get to the point of
update the reference of your branch to point to the new commit SHA
ie the line
var updateReferenceResponse = Patch<UpdateReferenceResponse>("git/refs/heads/master", updateReferenceRequest);
It fails with the response
<html><body><h1>502 Bad Gateway</h1>
The server returned an invalid or incomplete response.
</body></html>
Here is the specific API i am trying to call http://developer.github.com/v3/git/refs/#update-a-reference
Here is the main workings of the code
[Test]
public void UpdateFileUsingGithubDataApi()
{
var branch = GetUrlResponse<BranchResponse>("branches/master");
var currentCommitSha = branch.commit.sha;
var tree = GetUrlResponse<CommitResponse>("git/commits/" + currentCommitSha).tree;
var createBlob = new CreateBlobRequest
{
content = "sdkfn",
encoding = "utf-8"
};
var blobResponse = Post<CreateBlobResponse>("git/blobs", createBlob);
var blobSha = blobResponse.sha;
var createTreeRequest = new CreateTreeRequest
{
base_tree = tree.sha,
tree = new List<CreateTreeRequest.Tree>
{
new CreateTreeRequest.Tree
{
path = "README.md",
mode = "100644",
type = "blob",
sha = blobSha
}
}
};
var treeResponse = Post<CreateTreeResponse>("git/trees", createTreeRequest);
var createCommitRequest = new CreateCommitRequest
{
parent = new List<string>
{
currentCommitSha
},
message = "foo",
tree = treeResponse.sha
};
var commitResponse = Post<CreateCommitResponse>("git/commits", createCommitRequest);
var updateReferenceRequest = new UpdateReferenceRequest
{
sha = commitResponse.sha
};
var updateReferenceResponse = Patch<UpdateReferenceResponse>("git/refs/heads/master", updateReferenceRequest);
}
TResponse Post<TResponse>(string suffix, object value)
{
return Send<TResponse>(suffix, value, "Post");
}
TResponse Patch<TResponse>(string suffix, object value)
{
return Send<TResponse>(suffix, value, "Patch");
}
TResponse Send<TResponse>(string suffix, object value, string method)
{
var serializeObject = JsonConvert.SerializeObject(value, Formatting.Indented);
var sourceUrl = string.Format("https://api.github.com/repos/{0}/{1}/{2}", UserName, repo, suffix);
Debug.WriteLine("\r\n{0}ing to {1} with data\r\n{2}", method, sourceUrl, serializeObject);
var webRequest = WebRequest.Create(sourceUrl);
webRequest.Method = method;
AddAuth(webRequest);
var requestStream = webRequest.GetRequestStream();
using (var streamWriter = new StreamWriter(requestStream))
{
streamWriter.Write(serializeObject);
}
try
{
using (var webResponse = webRequest.GetResponse())
{
var text = webResponse.GetResponseStream().ReadToEnd();
Debug.WriteLine("response:\r\n" + text.GetPrettyPrintedJson());
return JsonConvert.DeserializeObject<TResponse>(text);
}
}
catch (WebException exception)
{
var readToEnd = exception.Response.GetResponseStream().ReadToEnd();
Debug.WriteLine("failed with response:\r\n" + readToEnd);
throw new Exception(readToEnd);
}
}
TResponse GetUrlResponse<TResponse>(string suffix)
{
var sourceUrl = string.Format("https://api.github.com/repos/{0}/{1}/{2}", UserName, repo, suffix);
var webRequest = WebRequest.Create(sourceUrl);
Debug.WriteLine("\r\nrequesting " + sourceUrl);
AddAuth(webRequest);
using (var webResponse = webRequest.GetResponse())
{
var text = webResponse.GetResponseStream().ReadToEnd();
Debug.WriteLine("response:\r\n"+ text.GetPrettyPrintedJson());
return JsonConvert.DeserializeObject<TResponse>(text);
}
}
void AddAuth(WebRequest webRequest)
{
if (UserName != null && Password != null)
{
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", UserName, Password)));
webRequest.Headers.Add("Authorization", string.Format("Basic {0}", token));
}
}
Here is a transcript of the http conversation
requesting https://api.github.com/repos/simoncropp/test/branches/master
response:
{
"name": "master",
"commit": {
"sha": "a4447748c9cd36601127e3a6143348a1695cc2e8",
"commit": {
"message": "Initial commit",
"tree": {
"sha": "5b3438cf3aca03901bdb2ae1722bc7e05738a7fe",
},
"comment_count": 0
},
},
}
requesting https://api.github.com/repos/simoncropp/test/git/commits/a4447748c9cd36601127e3a6143348a1695cc2e8
response:
{
"sha": "a4447748c9cd36601127e3a6143348a1695cc2e8",
"tree": {
"sha": "5b3438cf3aca03901bdb2ae1722bc7e05738a7fe",
},
"message": "Initial commit",
"parents": []
}
Posting to https://api.github.com/repos/simoncropp/test/git/blobs with data
{
"content": "sdkfn",
"encoding": "utf-8"
}
response:
{
"sha": "2b664114096f7ff36664e381c5fbd0030f47009c",
}
Posting to https://api.github.com/repos/simoncropp/test/git/trees with data
{
"base_tree": "5b3438cf3aca03901bdb2ae1722bc7e05738a7fe",
"tree": [
{
"path": "README.md",
"mode": "100644",
"type": "blob",
"sha": "2b664114096f7ff36664e381c5fbd0030f47009c"
}
]
}
response:
{
"sha": "fd1379d51016989a615acf79409256849dc8ea7f",
"tree": [
{
"mode": "100644",
"type": "blob",
"sha": "bdc3535f745bc86966fb24c67d252c3ea68e8e03",
"path": ".gitignore",
"size": 1522,
},
{
"mode": "100644",
"type": "blob",
"sha": "e0369aaa94e2bc8dce560c0ae0669d74204602d5",
"path": "LICENSE",
"size": 1078,
},
{
"mode": "100644",
"type": "blob",
"sha": "2b664114096f7ff36664e381c5fbd0030f47009c",
"path": "README.md",
"size": 5,
}
]
}
Posting to https://api.github.com/repos/simoncropp/test/git/commits with data
{
"message": "foo",
"tree": "fd1379d51016989a615acf79409256849dc8ea7f",
"parent": [
"a4447748c9cd36601127e3a6143348a1695cc2e8"
]
}
response:
{
"sha": "f66832493d22c58a6dd9d41b65504c1e9c901d7a",
"tree": {
"sha": "fd1379d51016989a615acf79409256849dc8ea7f",
},
"message": "foo",
"parents": []
}
Patching to https://api.github.com/repos/simoncropp/test/git/refs/heads/master with data
{
"sha": "f66832493d22c58a6dd9d41b65504c1e9c901d7a"
}
failed with response:
<html><body><h1>502 Bad Gateway</h1>
The server returned an invalid or incomplete response.
</body></html>
Posting to https://api.github.com/repos/simoncropp/test/git/commits with data
{
"message": "foo",
"tree": "fd1379d51016989a615acf79409256849dc8ea7f",
"parent": [
"a4447748c9cd36601127e3a6143348a1695cc2e8"
]
}
The docs are expecting a parents parameter here, not parent.
response:
{
"sha": "f66832493d22c58a6dd9d41b65504c1e9c901d7a",
"tree": {
"sha": "fd1379d51016989a615acf79409256849dc8ea7f",
},
"message": "foo",
"parents": []
}
Without it, you get an empty array of parent commits, and Bad Things Will Happen.

Categories

Resources