How to Get Attachment from SharePoint List in Azure Function using Webhooks - azure

We have a SharePoint list that contains a large PDF attachment and have set up an Azure Webhook to notify an Azure Function App of a change to the SharePoint list and would like to have the ability to check for and parse a PDF attachment.
I am fairly still new to this type of development, but have found an example that I followed, and our test currently seems to be triggering an event in our Azure Function and can see that the trigger is successful:
Monitor Invocation:
The code that I've implemented is pretty much verbatim from the above link example and it appears that most of the functionality is currently simply writing out log information, however I've not been able to find many examples of detailed implementation scenarios using SharePoint Lists to get an attachment from a SharePoint List.
Should we be getting our attachment data after reading in the request into a StreamReader object here?
var content = await new StreamReader(req.Body).ReadToEndAsync();
Full Code Context:
#r "Newtonsoft.Json"
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static async Task<IActionResult> Run(HttpRequest req,
ICollector<string> outputQueueItem, ILogger log)
{
log.LogInformation($"Webhook was triggered!");
// Grab the validationToken URL parameter
string validationToken = req.Query["validationtoken"];
// If a validation token is present, we need to respond within 5 seconds by
// returning the given validation token. This only happens when a new
// webhook is being added
if (validationToken != null)
{
log.LogInformation($"Validation token {validationToken} received");
return (ActionResult)new OkObjectResult(validationToken);
}
log.LogInformation($"SharePoint triggered our webhook...great :-)");
//Is the attachment available via the content variable?
var content = await new StreamReader(req.Body).ReadToEndAsync();
log.LogInformation($"Received following payload: {content}");
var notifications = JsonConvert.DeserializeObject<ResponseModel<NotificationModel>>(content).Value;
log.LogInformation($"Found {notifications.Count} notifications");
if (notifications.Count > 0)
{
log.LogInformation($"Processing notifications...");
foreach(var notification in notifications)
{
// add message to the queue
string message = JsonConvert.SerializeObject(notification);
log.LogInformation($"Before adding a message to the queue. Message content: {message}");
outputQueueItem.Add(message);
log.LogInformation($"Message added :-)");
}
}
// if we get here we assume the request was well received
return (ActionResult)new OkObjectResult($"Added to queue");
}
// supporting classes
public class ResponseModel<T>
{
[JsonProperty(PropertyName = "value")]
public List<T> Value { get; set; }
}
public class NotificationModel
{
[JsonProperty(PropertyName = "subscriptionId")]
public string SubscriptionId { get; set; }
[JsonProperty(PropertyName = "clientState")]
public string ClientState { get; set; }
[JsonProperty(PropertyName = "expirationDateTime")]
public DateTime ExpirationDateTime { get; set; }
[JsonProperty(PropertyName = "resource")]
public string Resource { get; set; }
[JsonProperty(PropertyName = "tenantId")]
public string TenantId { get; set; }
[JsonProperty(PropertyName = "siteUrl")]
public string SiteUrl { get; set; }
[JsonProperty(PropertyName = "webId")]
public string WebId { get; set; }
}
public class SubscriptionModel
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; set; }
[JsonProperty(PropertyName = "clientState", NullValueHandling = NullValueHandling.Ignore)]
public string ClientState { get; set; }
[JsonProperty(PropertyName = "expirationDateTime")]
public DateTime ExpirationDateTime { get; set; }
[JsonProperty(PropertyName = "notificationUrl")]
public string NotificationUrl {get;set;}
[JsonProperty(PropertyName = "resource", NullValueHandling = NullValueHandling.Ignore)]
public string Resource { get; set; }
}
I thought that I would try to debug this integration remotely since the code is running in the Azure Portal in order to set up watches and look into the HTTPRequest and StreamReader objects, however that's presented a whole new set of challenges and we've been unsuccessful there as well.
Thanks in advance.

What you've done here is fine, assuming you end up with an item placed on a queue. The real heavy lifting though will happen with the function that picks up the item off the queue. The json you send to the queue only notes that a change occurred; your queue receiver is going to have to authenticate and call back into SharePoint to fetch the data and do what it needs to do.
Read the reference implementation section of the documentation you linked for a better explanation of the webhooks architecture.
I will add that developing Azure functions directly in the portal is going to be a nightmare for anything but trivial applications. And your application is not trivial. The ngrok based approach in the Get Started section is good advice and really the only way to debug webhooks. Good luck!

Related

Is there a way to manage Azure Function Apps using Azure SDK?

I would like to write a web app that allows users to write C# scripts and execute them using Azure Functions.
I checked Azure SDK documentation and didn't find any nuget packages for managing Function Apps.
Is there a way I can:
retrieve list of available azure functions
deploy azure function
update azure function
delete azure function
using Azure SDK? If not, what would be another way to do it?
Update on Jul 8, 2019
I found List and Delete functions in Azure SDK (Microsoft.Azure.Management.WebSites):
List:
Msdn Documentation
Delete
Msdn Documentation
I tested them and they work.
The problem is with Create method _CreateFunctionWithHttpMessagesAsync (Msdn Documentation)
It is not clear which parameters should be passed for it to work.
Currently I call it like this:
var response = await webClient.WebApps.CreateFunctionWithHttpMessagesAsync(ResourceGroupName, FunctionAppName, functionName, new FunctionEnvelope());
In about 10-20s it returns error:
"Microsoft.Azure.Management.WebSites.Models.DefaultErrorResponseException : Operation returned an invalid status code 'InternalServerError'"
I think it is related to empty FunctionEnvelope. I tried passing various values, but none of them worked.
AFAIK there is no SDK's available. But there are REST API's which let you perform all the above operations.
List Functions
Delete Function
For updating and Deployment you can make use of the zip deployment for Azure Functions.
Generate the FunctionApp.zip using the msbuild command pointing to your csproj->
/p:DeployOnBuild=true /p:DeployTarget=Package;CreatePackageOnPublish=true
The above will generate a zip file which can be used in the later part.
Now 2nd step is to obtain the Publish credentials using this api, if you get the response it will in the below class format
public class GetPublishCredentials
{
public string Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public string Location { get; set; }
public Properties Properties { get; set; }
}
public class Properties
{
public string Name { get; set; }
public string PublishingUserName { get; set; }
public string PublishingPassword { get; set; }
public object PublishingPasswordHash { get; set; }
public object PublishingPasswordHashSalt { get; set; }
public object Metadata { get; set; }
public bool IsDeleted { get; set; }
public string ScmUri { get; set; }
}
After obtaining the credentials, follow the below piece of code to deploy or update your Azure Functions
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes
($"{functionCredentials.Properties.PublishingUserName}:{functionCredentials.Properties.PublishingPassword}"));
var stream = new MemoryStream(File.ReadAllBytes("zip file of azure function"));
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Basic " + base64Auth);
var apiUrl = "https://" + parameters.FunctionAppName + ".scm.azurewebsites.net/api/zip/site/wwwroot";
var httpContent = new StreamContent(stream);
client.PutAsync(apiUrl, httpContent).Result;
}
Now your functionapp should be deployed.

Api Post method not working in .net core azure production

I am using .net core for expose API. When I call api from postman, some method not hitting, get 404 not found error message.
[HttpPost]
public async Task<bool> AddLogs([FromBody]List<LogModel> model)
{
var result = false;
foreach (var item in model)
{
result = await _logService.Insert("Logs", item);
}
return result;
}
public class LogModel: TableEntity
{
public int Status { get; set; }
public bool IsBreak { get; set; }
public string Location { get; set; }
public DateTime StartDateAndTime { get; set; }
public DateTime EndDateAndTime { get; set; }
public string Remarks { get; set; }
public int Id { get; set; }
}
When I call the api 'AddLogs' , get not found error message.
But when try ,
[HttpPost]
public async Task<bool> Post()
{
return true;
}
It will return the true value.
But I noted that when I call in localhost 'AddLogs' api working fine. It will hit the api. But When I publish in azure, it shows me not found.
I test in my site and it works well.
The reason for this is that the deployment or default ASP.NET Core Web API template does not include a default document in the root directory of the web site. For example, index.htm, defualt.aspx, default.htm are default documents and IIS will deliver them if there is no specific file provided when accessing the URL.
You could set [HttpPost("AddLogs/")] to specify the AddLogs action if you have several httppost method. Remember also add the following code in Configure method.
app.UseMvc(routes =>
{
routes.MapRoute(name: "default", template: "api/{controller}/{action}/{id?}");
});

How to set GCM priority with Azure Notification Hub

I am currently using Azure Push Notification Service to send messages to android phones. According to This link you can set the priority of a GCM message to help deal with apps in Doze mode.
Here is how I currently use it:
string content = JsonConvert.SerializeObject(new GCMCarrier(data));
result = await Gethub().SendGcmNativeNotificationAsync(content, toTag);
here is GCMCarrier
public class GCMCarrier
{
public GCMCarrier(Object _data)
{
data = _data;
}
}
Now how do I add priority to the message? The constructor to send a GCM only has a data parameter?
Or can I simply add it to my `GCMCarrier" object along with data?
Give a try to the way someone use - add the Priority field to the payload. It was discussed recently in the Github repository as the issue. Windows Phone has that functionality in the SDK, while it looks like the Android does not. But Notification Hubs, AFAIK, is pass-through mechanism, so the payload will be treated by GCM itself.
You can enhance your current model and add required properties in correct format and then convert it to json payload.
public class GcmNotification
{
[JsonProperty("time_to_live")]
public int TimeToLiveInSeconds { get; set; }
public string Priority { get; set; }
public NotificationMessage Data { get; set; }
}
public class NotificationMessage
{
public NotificationDto Message { get; set; }
}
public class NotificationDto
{
public string Key { get; set; }
public string Value { get; set; }
}
Now you can convert your data with json converter but remember use lowercase setting in JsonConverter otherwise there might be expection on device. I have implementation of this in LowercaseJsonSerializer class.
private void SendNotification(GcmNotification gcmNotification,string tag)
{
var payload = LowercaseJsonSerializer.SerializeObject(gcmNotification);
var notificationOutcome = _hubClient.SendGcmNativeNotificationAsync(payload, tag).Result;
}
public class LowercaseJsonSerializer
{
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new LowercaseContractResolver()
};
public static string SerializeObject(object o)
{
return JsonConvert.SerializeObject(o,Settings);
}
public class LowercaseContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
return propertyName.ToLower();
}
}
}

How to get Chargify Webhook Response in .Net

I have already configure webhook url in chargify. This url is for webapi.
So i'm handling all events in webapi. But I want to know that how can we get the request parameter from chargify. If anyone have an example, would you please give me.
Below is the request from the chargify webhook's one event
you can get the below link for the webhook sending request for the events.
https://docs.chargify.com/webhooks#signup-success-payload
Please help me on this.
Thanks in Advance.
I tried the solution from above but it didn't work for me (probably because it's a 2015 solution and Chargify has made a few changes in the time).
What worked for me was:
[HttpPost]
[Route("test")]
[Consumes("application/x-www-form-urlencoded")]
public ActionResult Test([FromForm] RequestObject request)
If we will use RequestObject with ModelBinding, we have to create the data structure of the objects and variables we want to use.
For instance, for the signup_success event, the data structure for the objects Product, Customer and Customer Reference will be:
public class RequestObject
{
public string id { get; set; }
public Payload payload { get; set; }
}
public class Payload
{
public Subscription subscription { get; set; }
}
public class Subscription
{
public long id { get; set; }
public Product product { get; set; }
public Customer customer { get; set; }
}
public class Product
{
public long id { get; set; }
}
public class Customer
{
public long id { get; set; }
public string reference { get; set; }
}
Since it's submitted to the webhook url as form-parameters, so in MVC your signature would look similar to the following:
public ActionResult ReceiveWebhook(FormCollection webhookPayload, string signature_hmac_sha_256)
The parameter signature_hmac_sha_256 is included in the query string, so it's passed here.
You could then run different logic by using the event:
var eventName = webhookPayload["event"];

Json Deserialization BodyStyle Wrap issue using IPWorks nSoftware

I am using IPWorks nsoftware for creating service. In it, to call a service I am using
Rest rest = new Rest();
rest.Accept = "application/json";
rest.ContentType = "application/json";
rest.User = "UserName";
rest.Password = "Password";
rest.Get(#"http://Foo.com/roles.json");
string result = rest.TransferredData;
var listRoles = JsonSerializer.DeserializeFromString<List<role>>(result);
I am getting the Json response as a string
[{"role":{"name":"Administrator","created_at":"2012-02-11T09:53:54-02:00","updated_at":"2012-04-29T23:43:47-04:00","id":1"}},{"role":{"name":"NormalUser","created_at":"2013-02-11T08:53:54-02:00","updated_at":"2013-04-29T23:43:47-03:00","id":2"}}]
Here the json string contains my domain object “role” which gets appended to my response (i.e the body style of the message is wrapped) .
I am using ServiceStack.Text’s Deserializer to convert the response string to my object. But since it’s wrapped, the deserilization is incorrect.
Is there anything that I am missing here ? Is there any “BodyStyle” attribute which could be added to the Rest request?
The GitHubRestTests shows some of the different ways you can deserialize a 3rd party json API with ServiceStack's JSON Serializer.
If you want to deserialize it into typed POCOs then judging by your JSON payload the typed POCOs should look something like:
public class RolePermissionWrapper
{
public Role Role { get; set; }
public Permission Permission { get; set; }
}
public class Role
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime? Created_At { get; set; }
public DateTime? Updated_At { get; set; }
}
var listRoles = JsonSerializer.DeserializeFromString<List<RolePermissionWrapper>>(result);

Resources