Updating trigger frequency of Azure Logic App using API - c#

I am trying to update the recurrence frequency and interval of a Logic App using Azure Logic SDK and it is failing with this error message
Microsoft.Rest.Azure.CloudException: The request to patch workflow 'kk-test-logic-app' is not supported.
None of the fields inside the properties object can be patched.
Here is a code snippet showing what I am trying to do.
var workflow = await _client.Value.Workflows.GetAsync(resourceGroupName, workflowName);
dynamic workflowDefinition = workflow.Definition;
workflowDefinition.triggers[triggerName]["recurrence"] = JToken.FromObject(new { frequency = triggerFrequency, interval = triggerInterval });
await _client.Value.Workflows.UpdateAsync(resourceGroupName, workflowName, workflow);
where _client is Lazy<LogicManagementClient>.
Here is the definition of the trigger I am trying to update (got using Fiddler):
"triggers": {
"When_a_new_email_arrives": {
"recurrence": {
"frequency": "Hour",
"interval": 2
},
"splitOn": "#triggerBody()?.value",
"type": "ApiConnection",
"inputs": {
"host": {
"api": {
"runtimeUrl": "https://logic-apis-southindia.azure-apim.net/apim/office365"
},
"connection": {
"name": "#parameters('$connections')['office365']['connectionId']"
}
},
"method": "get",
"path": "/Mail/OnNewEmail",
"queries": {
"folderPath": "Inbox",
"importance": "Any"
}
}
}
}
Note that I am able to successfully retrieve the workflows, workflowRuns, workflowTriggers etc. Only the update operation is failing. Any ideas on how to update properties of workflows using the SDK?
UPDATE:
As pointed out by Amor-MSFT in the comments below, this is a defect and as a workaround, I am currently using CreateOrUpdateAsync instead of UpdateAsync. A new defect has been created in GitHub to get this to the attention of the SDK development team.

The trigger currently executes every 30s checking if a new mail was received from a certain email address and is working well as expected. I'm trying to change the recurrence frequency from 30s to 2hours using the code I provided.
I created a mail trigger and I can reproduce the issue if I use invoke UpdateAsync method. According to the source code of Azure Logic C# SDK, it send a PATCH request which is not supported according the response message. After changed the HTTP method to PUT, I can update the workflow. Here is the sample code which I used to send the PUT request.
string triggerName = "When_a_new_email_arrives";
string resourceGroupName = "my resourcegroup name";
string workflowName = "my-LogicApp";
string subscriptionID = "my-subscriptionID";
var workflow = await _client.Workflows.GetAsync(resourceGroupName, workflowName);
string url = string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Logic/workflows/{2}?api-version=2016-06-01",
subscriptionID, resourceGroupName, workflowName);
HttpClient client = new HttpClient();
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Put, url);
message.Headers.Add("Authorization", "Bearer put your token here");
message.Headers.Add("Accept", "application/json");
message.Headers.Add("Expect", "100-continue");
dynamic workflowDefinition = workflow.Definition;
workflowDefinition.triggers[triggerName]["recurrence"] = JToken.FromObject(new { frequency = "Minute", interval = 20 });
string s = workflow.ToString();
string workflowString = JsonConvert.SerializeObject(workflow, _client.SerializationSettings);
message.Content = new StringContent(workflowString, Encoding.UTF8, "application/json");
await client.SendAsync(message);

Related

SendGrid API returning 400 when I add more JSON properties

I'm trying to use the SendGrid API to create and schedule a Single Send email to a contact list when my Azure Functions endpoint is called. Here is the minimum amount of information that I can create the single send with and have it work with a 200 response.
private static SendGridClient _client = new SendGridClient(
Environment.GetEnvironmentVariable("SendGridApiKey")
);
...
var data = new {
name = "Title",
send_to = new {
list_ids = new [] {
$"{Environment.GetEnvironmentVariable("SendGridMailingId")}",
}
},
email_config = new {
design_id = $"{Environment.GetEnvironmentVariable("SendGridDesignId")}",
}
};
var singleSendResp = await _client.RequestAsync(
method: SendGridClient.Method.POST,
urlPath: "marketing/singlesends",
requestBody: JsonConvert.SerializeObject(data)
);
return singleSendResp;
The problem is that I'd like to include the send_at: "now", suppression_group_id, and sender_id, but if I include any of them (as well as all of them), I get this response:
{
"errors": [
{
"field": "",
"message": "json could not be unmarshalled"
}
]
}
I've tried all combinations of the above, and have even tried including all the properties that can't be null (minus the subject, html_content, etc since I have design_id.
I'm using the 9.28.1 SendGrid NuGet package, which should correspond to SendGrid's v3 API. I'm doing my testing locally with Postman. I am using the free version of the API, if that matters.
Any help would be appreciated. Thank you!
EDIT: Here would be my ideal object to send, with extra fields.
var data = new {
name = "Title",
send_at = "now",
send_to = new {
list_ids = new [] {
Environment.GetEnvironmentVariable("SendGridMailingId")
}
},
email_config = new {
design_id = Environment.GetEnvironmentVariable("SendGridDesignId"),
sender_id = Environment.GetEnvironmentVariable("SendGridSenderId"),
suppression_group_id = Environment.GetEnvironmentVariable("SendGridSuppressionId")
}
};
I finally figured out the issue, and it's a dumb mistake (as always). sender_id and suppression_group_id are supposed to be integers, but I was just sending the string. Wrapping the values in an int.Parse() works.
Also, even though send_at is supposed to be allowed a value of "now", it only works with the date string. For this, I included the following to format the string
string scheduleDate = DateTime.Now
.ToUniversalTime()
.AddMinutes(5)
.ToString("yyyy-MM-ddTHH:mm:ssZ");

How to manage a series of PATCH calls to Azure DevOps REST API to avoid HTTP Response Conflict error 409

In a custom C# Winforms app, I'm using the Azure DevOps REST API Update Comments call to update work item comments using async/await
My call to UpdateComment_Async is in a tight loop designed to submit all update comment requests for a single work item and then process each comment update as it completes.
Following is a mix of p-code and C# for (almost) working code. In the case where there are 2 (or more) comments to update, the first update returns HttpResponseMessage.Status 200 (success), but the second update submitted returns HttpResponseMessage.Status 409 "Conflict". I assume that ADS has the work item locked for the first update, and so the 2nd update fails with the 409. I think I proved this by sleeping the thread for 5 seconds after the call to UpdateComment_Async. With the sleep in place, both updates work.
Is there a way to manage the series of calls to UpdateComment_Async so that subsequent calls aren't done until the previous one is complete?
CODE
// get comment(s) for one work item .......
foreach (comment in workItem)
{
newCommentText = "new comment text blah-blah-blah";
Task<Tuple<string, string, HttpResponseMessage>> updateCommentTask = UpdateComment_Async(projectUrl, workItem.Id.ToString(), comment.Id.ToString(), newCommentText);
// I put a thread.sleep(5000) right here
updateCommentTaskList.Add(updateCommentTask);
}
// Process update comments tasks as they complete
while (updateCommentTaskList.Count > 0)
{
Task<Tuple<string, string, HttpResponseMessage>> finishedUpdateCommentTask = await Task.WhenAny(updateCommentTaskList);
// Get Results
Tuple<string, string, HttpResponseMessage> updateCommentTaskResult = finishedUpdateCommentTask.Result;
// process updateCommentTaskResult
// etc
// etc
// etc
updateCommentTaskList.Remove(finishedUpdateCommentTask);
}
//*******************************
public async Task<Tuple<string, string, HttpResponseMessage>> UpdateComment_Async(string projectUrl, string workItemId, string commentId, string commentNumber, string newCommentText)
{
HttpResponseMessage responseResult = null;
#region MAKE JSON REQUEST BODY
IList<ClsUpdateComment> updateFieldJsonList = new List<ClsUpdateComment>();
updateFieldJsonList.Clear();
// Note: This code works but is not in compliance with MS docs on 2 counts.
//
// 1) The MS docs on comment update say that the body should look like fig 1.
// The only I could do this was to create a new ClsUpdateComment and then add it to
// List<ClsUpdateComment>. Adding the ClsUpdateComment object to a list causes the [ ]
// to be created when the list is serialized. To make this work, I had to serialize
// just the ClsUpdateComment object so that when serialzed, it ends up looking like Fig 2 (no brackets)
//
// 2) application/json-patch+json causes error 415 - unsupported media type to occur. application/json
// works.
/*
Fig 1
[
{
"text": "Moving to the right area path - Fabrikam-Git"
}
]
Fig 2
{
"text": "Moving to the right area path - Fabrikam-Git"
}
*/
ClsUpdateComment updateFieldJson = new ClsUpdateComment
{
Text = $"{newCommentText}"
};
updateFieldJsonList.Add(updateFieldJson);
#endregion MAKE JSON REQUEST BODY
#region SUBMIT UPDATE REQUEST
string request = $"{projectUrl}/_apis/wit/workitems/{workItemId}/comments/{commentId}?api-version=5.1-preview.3";
JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
{
StringEscapeHandling = StringEscapeHandling.EscapeHtml,
};
string updateFieldJsonSerialized = JsonConvert.SerializeObject(updateFieldJson, Formatting.None, jsonSerializerSettings);
using (HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, ClientCertificateOptions = ClientCertificateOption.Manual }))
{
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(new HttpMethod("PATCH"), request)
{
Content = new StringContent(updateFieldJsonSerialized, Encoding.UTF8, "application/json")
};
using (responseResult = await client.SendAsync(httpRequestMessage))
{
// don't need Content, just the HttpResponseMessage
// //string content = await responseResult.Content.ReadAsStringAsync();
}
}
#endregion SUBMIT UPDATE REQUEST
return Tuple.Create(workItemId, commentNumber, responseResult);
}
It's not an answer, but after trying to do mass updates of comments using the REST API in various ways, I've come to the conclusion that it simply doesn't support mass updates of comments

MS Graph API/Workbooks (Excel API): Updating a cell format in a UseRange

What's I'm doing well
I'm currently creating an dotnet core app to consume and process data from an Excel sheet stored in personal OneDrive. I'm using MSAL to create a session token and the data consumption is working great. Here's my working code:
// Get the range for data to process
var dataRangeRequest = myGraphServiceClient // an instance of GraphServiceClient
.Me.Drive.Items[fileItemId]
.Workbook
.Sheets[sheetId]
.UsedRange(valuesOnly: false)
.Request();
var dataRange = await dataRangeRequest.GetAsync(ct)
// Extract column names (headers) from the data range
var columnNames = dataRange.Text.First.ToObject<string[]>();
// Extract data cells from the data range
var lines = dataRange.Text.Skip(1).Select(line=>line.ToObject<string[]>).ToArray();
[...] // Here I process the lines using the columnNames.
// --EVERYTHING WORKS UNTIL HERE--
What I'm not doing well
Now, I want to turn red a faulty data cell in the original Excel document
var faultyCell = (row: 34, column: 5); // the row/column offset of the faulty cell in dataRange
// ---------------------------------
// --FOLLOWING CODE IS NOT WORKING--
// ---------------------------------
var changeRange = new WorkbookRange
{
RowIndex = faultyCell.row,
ColumnIndex = faultyCell.column,
RowCount = 1,
ColumnCount = 1,
Format = new WorkbookRangeFormat {Fill = new WorkbookRangeFill {Color = "red"}}
};
await dataRangeRequest.PatchAsync(changeRange, ct); // Throwing a Microsoft.Graph.ServiceException
I intercepted the HTTP request & response and it's the following:
REQUEST
PATCH https://graph.microsoft.com/v1.0/me/drive/items/<file id>/workbook/worksheets/{sheetId}/microsoft.graph.usedRange(valuesOnly=true) HTTP/1.1
{
"columnIndex": 5,
"rowIndex": 34,
"columnCount": 1,
"rowCount": 1,
"format": {
"fill": {
"color": "Red",
"#odata.type": "microsoft.graph.workbookRangeFill"
},
"#odata.type": "microsoft.graph.workbookRangeFormat"
},
"#odata.type": "microsoft.graph.workbookRange"
}
RESPONSE
400 Bad Request
{
"error": {
"code": "BadRequest",
"message": "Bad Request - Error in query syntax.",
"innerError": {
"date": "<the date>",
"request-id": "<a guid>",
"client-request-id": "<another guid>"
}
}
}
Success with a manual HTTP request
I succeed to update manually the cell using a HTTP request by following the documentation.
WORKING REQUEST:
PATCH https://graph.microsoft.com/v1.0/me/drive/items/<file id>/workbook/worksheets/{sheetId}/range(address='F35:F35')/format/fill
{"color": "red"}
Problems
I don't know how to generate this HTTP request from C# by using the Microsoft.Graph api. (the documentation is obsoleted, there's no .Format on IWorkbookWorksheetRangeRequestBuilder. This error seems documented on GitHub. Is there an easy way to use the graph SDK to send an arbritary http request?
More importantly: for this to work, I need to translate the cell offset to a range address. Is there an utility somewhere to do that? In my example I manually translated the offset 5,34 in the range to address F35.
Specifications
Packages:
Microsoft.Graph: v3.15.0 (latest release version)
Microsoft.Identity.Client: (MSAL) v4.15.0 (not the latest version, but shoudn't be a problem here)
I can confirm, that this issue with no .Format on IWorkbookWorksheetRangeRequestBuilder is frustrating for me as well ;) I've reported this issue on GitHub:
https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/233
For me the workaround was to do it like so (i was basing it on this SOF: REST call to Microsoft Graph
var requestUrl = $#"https://graph.microsoft.com/v1.0/users/{user id}/drive/items/{Consts.DriveId}/workbook/worksheets/{Consts.SheetId}/range(address='{range}')/{resource}";
string workbookRangeFill = GraphServiceClient.HttpProvider.Serializer.SerializeObject(workbookRange);
// Create the request message and add the content.
HttpRequestMessage hrm = new HttpRequestMessage(new HttpMethod(httpMethod), requestUrl);
hrm.Content = new StringContent(workbookRangeFill, System.Text.Encoding.UTF8, "application/json");
// Authenticate (add access token) our HttpRequestMessage
await GraphServiceClient.AuthenticationProvider.AuthenticateRequestAsync(hrm);
// Send the request and get the response.
HttpResponseMessage response = await GraphServiceClient.HttpProvider.SendAsync(hrm);
Whereas workbookRange variable is of either of type: WorkbookRangeFill or WorkbookRangeFont (Depending on the need) I assume you will be interested in WorkbookRangeFill (to change the color in range/cell)
range variable is range in spreadsheet in the format: "A1:B3"
resource variable is format/fill for WorkbookRangeFill and format/font for WorkbookRangeFont
and of course {user id} is the user which owns the document (i am using client credentials flow with with application permisions For other scenarios i assume you can just change one thing in the code above. So that, instead of:
https://graph.microsoft.com/v1.0/users/{user id}/drive
to use
https://graph.microsoft.com/v1.0/me/drive

Web push C# library function

I am currently working on web push notifications and am at the last stage of using the web push libraries to send the notification.
I am using the C# web push library here. However, I do not see a notification when on the page or when not on it.
I am attaching my code below:
I wrote the code in my store subscription method so it could be one of the issues.
[HttpPost]
public void StoreSubscription(string [] publicKey, string [] auth, string notificationEndPoint )
{
var pushEndpoint = notificationEndPoint;
var pushAuth = auth[0].ToString();
var pushP256DH = publicKey[0].ToString();
var subject = "mailTo:hhhhhhh#gmail.com";
var uPublicKey = "yyzzxx";
var privateKey = "xxyyzz";
System.IO.File.WriteAllText(#"\Desktop\Subscription.txt", pushEndpoint);
var subscription = new PushSubscription(pushEndpoint, pushP256DH, pushAuth);
var gcmAPIKey = "AAAA";
var vapidDetails = new VapidDetails(subject, uPublicKey, privateKey);
var webPushClient = new WebPushClient();
try
{
webPushClient.SetGCMAPIKey(gcmAPIKey);
webPushClient.SendNotification(subscription, "payload", gcmAPIKey);
}
catch (WebPushException exception)
{
Console.WriteLine("HTTP status Code:" + exception.StatusCode);
}
}
And my service worker code is as follows for handling the push:
self.addEventListener('push', function (event) {
debugger
var body;
if (event.data) {
body = event.data.text();
} else {
body = 'Push message no payload';
}
var options = {
body: body,/*'This message was generated from a push'*/
icon: '/Images/noun_Pushing_1823359.png',
vibrate: [100, 200, 100, 200, 400],
data: {
dateOfArrival: Date.now(),
primaryKey: '2'
},
actions: [
{
action: 'explore', title: 'Explore this new world',
icon: '/Images/noun_Check Mark_4870.png'
},
{
action: 'close', title: 'Close',
icon: '/Images/noun_Close_887590.png'
},
]
};
event.waitUntil(
self.registration.showNotification('Push Notification', options)
);
});
I have been stuck on this for almost a long time now and very new to promise, service worker and push and notification apis.
the function is getting hit and does not throw any exception. Also, when i put a debugger in the service worker, it does not get hit so apparently the push is not getting handles.I might be completely wrong on this.
I had the same issue. I base 64 encoded my auth and p256dh used to create the subscription.
If there is no exception it could mean the JSON payload is valid JSON but in the wrong format for the service worker. I Changed my payload to a valid JSON object for my service worker (Angular):
{ "notification": {"title": "message title", "body": "message body"} }
Please see docs... https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification
Sent notification and hit the service worker.
I was using Vapid Details not GCMApiKey.

How to programatically apply an existing SSL certificate to an Azure web app

I'm using the Azure Fluent Management API to automate our deployment process. Up until now, I've had minimal problems.
We have SSL certificates already uploaded into Azure and can manually bind them to a web site through the Azure portal. But I can't find a mechanism for doing this programmatically.
The closest I can find is below and in the documentation here.
webApp.Update()
.DefineSslBinding()
.ForHostname(domainName)
.WithPfxCertificateToUpload(pfxFile, password)
.WithSniBasedSsl()
.Attach();
However, this is obviously uploading a new certificate, not using an existing one.
There are two other options after the ForHostName() call:
WithExistingAppServiceCertificateOrder(certificateOrder)
and
WithNewStandardSslCertificateOrder(certificateOrderName)
But my understanding is that these are related to purchasing the certificates through Azure/Microsoft.
I also can't see anything in the REST API documentation.
So, how can I associate an existing certificate with a web app, in code?
Obviously this was not critical given I've only found an answer 9 months later.
Anyhow, the answer below is copied from the link provided.
await azure
.WebApps
.Inner
.CreateOrUpdateHostNameBindingWithHttpMessagesAsync(
resourceGroupName,
webAppName,
domain,
new HostNameBindingInner(
azureResourceType: AzureResourceType.Website,
hostNameType: HostNameType.Verified,
customHostNameDnsRecordType: CustomHostNameDnsRecordType.CName,
sslState: SslState.SniEnabled,
thumbprint: thumbprint));
As far as I know, the Azure Fluent Management API’s version is 1.0.0-beta50, so it maybe not contain the method add existing certificate to the host name.
I suggest you could use REST API to achieve it.
I suggest you could send request to below url.
Url: https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{snapshotName}?api-version={api-version}
Method: PUT
Parameter:
subscriptionId The identifier of your subscription where the snapshot is being created.
resourceGroup The name of the resource group that will contain the snapshot.
WebappName The name of the WebappName.
api-version The version of the API to use.
Request content:
{
"properties": {
"HostNameSslStates": [
{
"SslState": "the SSL state",
"ToUpdate": "True",
"Thumbprint": "The Thumbprint of the certificate, you could find it in the portal",
"Name": "yourwebsitename"
}
]
},
"kind": "app",
"location": "yourlocation",
"tags": {
"hidden-related:/subscriptions/{subscriptionId}/resourcegroups/{resourceGroup}/providers/Microsoft.Web/serverfarms/{yourserviceplan}": "empty"
}
}
More details, you could refer to below C# codes:
Json.txt:
{
"properties": {
"HostNameSslStates": [
{
"SslState": "1",
"ToUpdate": "True",
"Thumbprint": "BE58B05C5CADE03628D0D58B369D0DA6F535B0FA",
"Name": "test.azureclubs.com"
}
]
},
"kind": "app",
"location": "East Asia",
"tags": {
"hidden-related:/subscriptions/xxxxxxxxxxxxxxxx/resourcegroups/xxxxxxxxxxxxx/providers/Microsoft.Web/serverfarms/BrandoTestServicePlan": "empty"
}
}
Code:
string body = File.ReadAllText(#"D:\json.txt");
// Display the file contents to the console. Variable text is a string.
string tenantId = "xxxxxxxxxxxxxxxxxxxxxxxxx";
string clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
string clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxx";
string subscriptionid = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
string resourcegroup = "BrandoSecondTest";
string appname = "BrandoTestApp";
string version = "2015-08-01";
string authContextURL = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextURL);
var credential = new ClientCredential(clientId, clientSecret);
var result = authenticationContext.AcquireTokenAsync(resource: "https://management.azure.com/", clientCredential: credential).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Web/sites/{2}?api-version={3}", subscriptionid, resourcegroup, appname, version));
request.Method = "PUT";
request.Headers["Authorization"] = "Bearer " + token;
request.ContentType = "application/json";
try
{
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(body);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// Get the response
var httpResponse = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
Console.WriteLine(streamReader.ReadToEnd());
}
This solution works in 2021. You only need to know the thumbprint of your certificate and it should be in the same resource group as your web app.
var webApp = azure.WebApps
.GetById("webapp resource Id goes here")
.Update()
.DefineSslBinding()
.ForHostname("host name goes here")
.WithExistingCertificate("thumbprint goes here")
.WithSniBasedSsl()
.Attach()
.Apply();

Categories