Azure Function Sengrid - Attachment only working locally - azure

I'm trying to add a file to a mail sent by an azure function.
Here my function :
#r "Newtonsoft.Json"
#r "SendGrid"
#r "System.Web"
using System.Web;
using SendGrid.Helpers.Mail;
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.Azure.WebJobs.Host;
using System;
public static IActionResult Run(HttpRequest req, TraceWriter log, out SendGridMessage message)
{
log.Info("C# HTTP trigger function processed a request.");
string requestBody = new StreamReader(req.Body).ReadToEnd();
EmailContent data = JsonConvert.DeserializeObject<EmailContent>(requestBody);
if (data == null)
{
throw new ArgumentNullException("Data could not be null");
}
message = new SendGridMessage();
message.AddTo(data.Email);
message.SetFrom(new EmailAddress("no-reply#netflio.com"));
message.AddContent("text/html", HttpUtility.HtmlDecode(data.Body));
message.AddAttachment(data.AttachmentName, Convert.ToBase64String(data.Attachment));
message.SetSubject(data.Subject);
return (ActionResult)new OkObjectResult("OK");
}
public class EmailContent
{
public string Email { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public byte[] Attachment { get; set; }
public string AttachmentName { get; set; }
}
My function works very well in my local machine but not on my azure function.
The file is missing...

I test it and it works well both in locally and on azure. You could refer to the steps as below:
1.Create a HttpTrigger on portal and configure Outputs.
2.Add sendgrid function in HttpTrigger. As you have set up the Outputs, ToAddress, FromAddress, MessageText and MessageSubject all have contain.
3.The output.

Related

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

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!

Azure tranlate API call is returning and 40100

I used this HTTP-Get request to get a Bearer token for the translation:
https://api.cognitive.microsoft.com/sts/v1.0/issueToken?Subscription-Key=1fo8xxx
Using the returned Bearer I wanted to translate a short text using this API endpoint:
https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=de
In the header I put this:
Content-Type: application/json; charset=UTF-8.
And in the body I put this:
[
{"Text":"I would really like to drive your car around the block a few times."}
]
I am using Postman, so in the authorization tab I selected Bearer and inserted in the field next to it this:
Bearer <result from the first API call>
If I send the reqeuest I get this result:
{"error":{"code":401000,"message":"The request is not authorized because credentials are missing or invalid."}}
In case someone ever stumbles upon this, after hours of trial and error I found out you need to pass the Ocp-Apim-Subscription-Region param in the header.
Here is an example in python that I was able to run successfully.
import json
import requests
def translate(text, source_language, dest_language):
if not <Secret Key>:
return 'Error: the translation service is not configured.'
headers = {'Ocp-Apim-Subscription-Key': <Secret Key>,
'Ocp-Apim-Subscription-Region': <region>,
'Content-type': 'application/json'}
url = 'https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from={}&to={}' \
.format(source_language, dest_language)
body = [{'text': text}]
request = requests.post(url, headers=headers, json=body)
if request.status_code != 200:
return 'Error: the translation service failed.'
return json.loads(request.content.decode('utf-8-sig'))
The list of regions and other examples can be found here:
https://learn.microsoft.com/en-us/azure/cognitive-services/translator/reference/v3-0-reference
Don't be fooled by the curl example that is not using the region..
Your request needs the "OCP-Apim-Subscription-Key" header. Take a look on the official example:
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
// Install Newtonsoft.Json with NuGet
using Newtonsoft.Json;
/// <summary>
/// The C# classes that represents the JSON returned by the Translator Text API.
/// </summary>
public class TranslationResult
{
public DetectedLanguage DetectedLanguage { get; set; }
public TextResult SourceText { get; set; }
public Translation[] Translations { get; set; }
}
public class DetectedLanguage
{
public string Language { get; set; }
public float Score { get; set; }
}
public class TextResult
{
public string Text { get; set; }
public string Script { get; set; }
}
public class Translation
{
public string Text { get; set; }
public TextResult Transliteration { get; set; }
public string To { get; set; }
public Alignment Alignment { get; set; }
public SentenceLength SentLen { get; set; }
}
public class Alignment
{
public string Proj { get; set; }
}
public class SentenceLength
{
public int[] SrcSentLen { get; set; }
public int[] TransSentLen { get; set; }
}
private const string key_var = "TRANSLATOR_TEXT_SUBSCRIPTION_KEY";
private static readonly string subscriptionKey = Environment.GetEnvironmentVariable(key_var);
private const string endpoint_var = "TRANSLATOR_TEXT_ENDPOINT";
private static readonly string endpoint = Environment.GetEnvironmentVariable(endpoint_var);
static Program()
{
if (null == subscriptionKey)
{
throw new Exception("Please set/export the environment variable: " + key_var);
}
if (null == endpoint)
{
throw new Exception("Please set/export the environment variable: " + endpoint_var);
}
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
// Build the request.
// Set the method to Post.
request.Method = HttpMethod.Post;
// Construct the URI and add headers.
request.RequestUri = new Uri(endpoint + route);
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
request.Headers.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
// Send the request and get response.
HttpResponseMessage response = await client.SendAsync(request).ConfigureAwait(false);
// Read response as a string.
string result = await response.Content.ReadAsStringAsync();
// Deserialize the response using the classes created earlier.
TranslationResult[] deserializedOutput = JsonConvert.DeserializeObject<TranslationResult[]>(result);
// Iterate over the deserialized results.
foreach (TranslationResult o in deserializedOutput)
{
// Print the detected input language and confidence score.
Console.WriteLine("Detected input language: {0}\nConfidence score: {1}\n", o.DetectedLanguage.Language, o.DetectedLanguage.Score);
// Iterate over the results and print each translation.
foreach (Translation t in o.Translations)
{
Console.WriteLine("Translated to {0}: {1}", t.To, t.Text);
}
}
}
Console.Read();
}
https://learn.microsoft.com/en-us/azure/cognitive-services/translator/quickstart-translate?pivots=programming-language-csharp

Azure Easy Tables - Load only one column

Is there some way to get only one data column for one row from Azure Easy Tables?
For example Xamarin.Forms app will send name of item to Azure and get the item creation DateTime only.
Here's an example where we want to select just the Name Column from our Dog Table.
This sample uses the Azure Mobile Client and the Azure Mobile Client SQL NuGet Packages.
Model
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json;
namespace SampleApp
{
public class Dog
{
public string Name { get; set; }
public string Breed { get; set; }
public int Age { get; set; }
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[CreatedAt]
public DateTimeOffset CreatedAt { get; set; }
[UpdatedAt]
public DateTimeOffset UpdatedAt { get; set; }
[Version]
public string AzureVersion { get; set; }
[Deleted]
public bool IsDeleted { get; set; }
}
}
Logic
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.WindowsAzure.MobileServices;
using Microsoft.WindowsAzure.MobileServices.Sync;
using Microsoft.WindowsAzure.MobileServices.SQLiteStore;
namespace SampleApp
{
public class MobileClientService
{
bool isMobileClientInitialized;
MobileServiceClient mobileClient;
public async Task<string> GetDogName(string id)
{
await InitializeMobileClient();
var dog = await mobileClient.GetSyncTable<Dog>().LookupAsync(id);
var dogName = dog.Name;
return dogName;
}
public async Task<IEnumerable<string>> GetDogNames()
{
await InitializeMobileClient();
var dogNameList = await mobileClient.GetSyncTable<Dog>().Select(x => x.Name).ToEnumerableAsync();
return dogNameList;
}
async Task InitializeMobileClient()
{
if(isMobileClientInitialized)
return;
mobileClient = new MobileServiceClient("Your Azure Mobile Client Url");
var path = Path.Combine(MobileServiceClient.DefaultDatabasePath, "app.db");
var store = new MobileServiceSQLiteStore(path);
store.DefineTable<Dog>();
//ToDo Define all remaining tables
await MobileServiceClient.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
}
}
}

servicestack AppHostHttpListenerBase handlerpath parameter not working?

not sure if I am missing something here. I am using the AppHostHttpListenerBase in a unit test to test a service and in its constructor I pass "api" for the handlerPath parameter. I have a service registered at /hello/{Name} and am using version 3.9.17 of servicestack.
Within the Config method of my appHost class if I access
EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath
it retrurns "api"
Once I am back in the unit test the same call returns null
If I try and call the service with /hello/test it works.
If I use /api/hello/test it fails
It appears that the AppHostHttpListenerBase is loosing the handlerPath ?
Does this sound like a bug or am I missing something ?
below is the code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
namespace Bm.Tests
{
/// <summary>
/// Test self hosting for unit tests
/// </summary>
[TestFixture]
public class TestService
{
private TestServiceAppHost _apphost;
private const string HOST_URL = #"http://localhost:1337/";
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
_apphost = new TestServiceAppHost();
_apphost.Init();
_apphost.Start(HOST_URL);
}
[Test]
public void TestHelloServiceJson()
{
var prefix = EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath;
Assert.AreEqual("api", prefix, "Should be api");
var client = new JsonServiceClient(HOST_URL);
var response = client.Send<HelloResponseTest>(new HelloTest() { Name = "Todd" });
Assert.AreEqual("Hello, Todd", response.Result);
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
_apphost.Stop();
_apphost.Dispose();
}
}
public class HelloTest
{
public string Name { get; set; }
}
public class HelloResponseTest
{
public string Result { get; set; }
}
public class HelloServiceTest : ServiceBase<HelloTest>
{
protected override object Run(HelloTest request)
{
return new HelloResponseTest { Result = "Hello, " + request.Name };
}
}
//Define the Web Services AppHost
public class TestServiceAppHost : AppHostHttpListenerBase
{
public TestServiceAppHost() : base("testing HttpListener", "api", typeof(HelloServiceTest).Assembly) { }
public override void Configure(Funq.Container container)
{
// this works and returns api
var prefix = EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath;
Routes
.Add<HelloTest>("/hello")
.Add<HelloTest>("/hello/{Name}");
}
}
}
If you want the handler root path to be /api you need to add that to the listener url, i.e:
_apphost.Start("http://localhost:1337/api/");

Configure ServiceStack Base URI

I'm creating a self-hosted REST service using service stack & AppHostHttpListenerBase. I'd like to use a base URI for my services (e.g. "api") like so:
http://myserver/api/service1/param
http://myserver/api/service2/param
How do I do this without defining "api" in each of my routes. In IIS, I can set a virtual directory to isolate the services, but how do I do this when self-hosting?
Here ya go.. (as a bonus this is how you put your service into a plugin.
using BlogEngineService;
using ServiceStack.WebHost.Endpoints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BlogEngineWinService
{
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("Self Host Service", typeof(AppHost).Assembly) { }
public override void Configure(Funq.Container container)
{
Plugins.Add(new BlogEngine());
}
}
}
This is how you autowire it up
The call appHost.Routes.AddFromAssembly2(typeof(HelloService).Assembly); Is what calls the extension to auto wire.
using ServiceStack.WebHost.Endpoints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceStack.ServiceInterface;
namespace BlogEngineService
{
public class BlogEngine : IPlugin, IPreInitPlugin
{
public void Register(IAppHost appHost)
{
appHost.RegisterService<HelloService>();
appHost.Routes.AddFromAssembly2(typeof(HelloService).Assembly);
}
public void Configure(IAppHost appHost)
{
}
}
}
This is how you mark the Service Class to give it a prefix.
Simply mark the class with this attribute
using ServiceStack.DataAnnotations;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BlogEngineService
{
public class Hello
{
[PrimaryKey]
public string Bob { get; set; }
}
public class HelloResponse
{
public string Result { get; set; }
}
[PrefixedRoute("/test")]
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Bob};
}
}
}
Create a CS file in your project for the extension..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using ServiceStack.Common;
using ServiceStack.Common.Utils;
using ServiceStack.Common.Web;
using ServiceStack.Text;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost.Endpoints;
using ServiceStack.ServiceInterface;
namespace ServiceStack.ServiceInterface
{
public static class ServiceRoutesExtensions
{
/// <summary>
/// Scans the supplied Assemblies to infer REST paths and HTTP verbs.
/// </summary>
///<param name="routes">The <see cref="IServiceRoutes"/> instance.</param>
///<param name="assembliesWithServices">
/// The assemblies with REST services.
/// </param>
/// <returns>The same <see cref="IServiceRoutes"/> instance;
/// never <see langword="null"/>.</returns>
public static IServiceRoutes AddFromAssembly2(this IServiceRoutes routes,
params Assembly[] assembliesWithServices)
{
foreach (Assembly assembly in assembliesWithServices)
{
AddNewApiRoutes(routes, assembly);
}
return routes;
}
private static void AddNewApiRoutes(IServiceRoutes routes, Assembly assembly)
{
var services = assembly.GetExportedTypes()
.Where(t => !t.IsAbstract
&& t.HasInterface(typeof(IService)));
foreach (Type service in services)
{
var allServiceActions = service.GetActions();
foreach (var requestDtoActions in allServiceActions.GroupBy(x => x.GetParameters()[0].ParameterType))
{
var requestType = requestDtoActions.Key;
var hasWildcard = requestDtoActions.Any(x => x.Name.EqualsIgnoreCase(ActionContext.AnyAction));
string allowedVerbs = null; //null == All Routes
if (!hasWildcard)
{
var allowedMethods = new List<string>();
foreach (var action in requestDtoActions)
{
allowedMethods.Add(action.Name.ToUpper());
}
if (allowedMethods.Count == 0) continue;
allowedVerbs = string.Join(" ", allowedMethods.ToArray());
}
if (service.HasAttribute<PrefixedRouteAttribute>())
{
string prefix = "";
PrefixedRouteAttribute a = (PrefixedRouteAttribute)Attribute.GetCustomAttribute(service, typeof(PrefixedRouteAttribute));
if (a.HasPrefix())
{
prefix = a.GetPrefix();
}
routes.AddRoute(requestType, allowedVerbs, prefix);
}
else
{
routes.AddRoute(requestType, allowedVerbs);
}
}
}
}
private static void AddRoute(this IServiceRoutes routes, Type requestType, string allowedVerbs, string prefix = "")
{
var newRoutes = new ServiceStack.ServiceHost.ServiceRoutes();
foreach (var strategy in EndpointHost.Config.RouteNamingConventions)
{
strategy(newRoutes, requestType, allowedVerbs);
}
foreach (var item in newRoutes.RestPaths)
{
string path = item.Path;
if (!string.IsNullOrWhiteSpace(prefix))
{
path = prefix + path;
}
routes.Add(requestType, restPath: path, verbs: allowedVerbs);
}
}
}
public class PrefixedRouteAttribute : Attribute
{
private string _prefix { get; set; }
private bool _hasPrefix { get; set; }
public PrefixedRouteAttribute(string path)
{
if (!string.IsNullOrWhiteSpace(path))
{
this._hasPrefix = true;
this._prefix = path;
//this.Path = string.Format("/{0}{1}", Prefix, Path);
}
}
public bool HasPrefix()
{
return this._hasPrefix;
}
public string GetPrefix()
{
return this._prefix;
}
}
}
ServiceStack's HttpListener hosts expects to be hosted a the root / path as the normal use-case is to have each self-hosted service available on different custom ports.
Since it doesn't currently support hosting at a /custompath, you would have to specify /api/ prefix on all your service routes.
Add an issue if you want to see support for hosting at custom paths.
There is actually an easier solution. In your web.config, update your http-handler to:
<httpHandlers>
<add path="api*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" />
</httpHandlers>
With the above, all of your service apis must be prefixed with a "/api/". If you have already used "/api/" in any of your routes, you must now remove them or have to specify it twice in your calls.
Reference:
https://github.com/ServiceStack/SocialBootstrapApi
I've found a workaround for this. I've only tested this under self hosting.
Create a 'PrefixedRouteAttribute' class that inherits from RouteAttribute
public class PrefixedRouteAttribute : RouteAttribute
{
public static string Prefix { get; set; }
public PrefixedRouteAttribute(string path) :
base(path)
{
SetPrefix();
}
public PrefixedRouteAttribute(string path, string verbs)
: base(path, verbs)
{
SetPrefix();
}
private void SetPrefix()
{
if (!string.IsNullOrWhiteSpace(Prefix))
{
this.Path = string.Format("/{0}{1}", Prefix, Path);
}
}
}
When you create your AppHost you can set your Prefix
PrefixedRouteAttribute.Prefix = "api";
Then instead of using the [Route] attribute, use the [PrefixRoute] attribute on your classes
[PrefixedRoute("/echo")]
[PrefixedRoute("/echo/{Value*}")]
public class Echo
{
[DataMember]
public string Value { get; set; }
}
This will then work for requests to
/api/echo
/api/echo/1
This could possibly be improved. I don't really like the how I need to set the Prefix via the static property but I couldn't think of a better approach under my setup. The principle of creating the overriding attribute seems sound though, and that is the important part.

Resources