[:es]Una de las ventajas más grandes de trabajar con Azure Functions es poder hacer fácilmente prototipos de aplicaciones. Para este post voy a crear una aplicación de Galleta de la suerte bastante simple, que enviará una frase por email usando Sendgrid y por SMS usando Twilio. La app se compone de tres piezas
- Página de Front-end usando HttpTrigger
- Request POST procesado con HttpTrigger
- Queue procesado usando el Trigger de Azure Queue Storage
Front-end
Usaremos el trigger de HTTP para el front-end. Dado que sólo será una página, realmente no necesitamos crear y mantener una aplicación web completa. Entonces, creamos una función de tipo HTTP Trigger
El nombre puede ser cualquier cosa. En este caso lo importante es definir el valor de Authorization Level a Anonymous, dado que la app será pública.
Una vez que la función se crea, como puede verse está con un código como plantilla. Elimina el código excepto la línea de log. Dado que queremos regresar un tipo de respuesta diferente, primero necesitamos crear el objeto de HttpResponseMessage
HttpResponseMessage m = req.CreateResponse(HttpStatusCode.OK);
Debe notarse que estamos asignado el resultado de CreateResponse
a una variable, en lugar de regresarlo directamente como estaba antes. Esto es porque queremos modificar el valor de Content
, diciendo que será de tipo text/html
m.Content = new StringContent(sb.ToString(), Encoding.UTF8, "text/html");
sb es un objeto de tipo< code >StringBuilder que nos ayudará a crear el HTML que queremos regresar. También podríamos crearlo usando
HtmlTextWriter
, pero para este ejemplo prefiero tener un string
para visualizar mejor lo que se pretende realizar.
En la linea 40 estoy usando string interpolation, para pasar el valor correspondiente a la URL que va a procesar los requests. Este valor se tomará de los AppSettings,
string httptriggerurl = ConfigurationManager.AppSettings["httptriggerurl"];
Con esto se completa la parte del front-end.
Procesando los requests
Crea una nueva función de tipo HTTP Trigger, y esta vez el valor de Authorization level que sea Function. La función (nuevamente) contiene una plantilla de código. Está pensado para leer los datos ya sea del query string o del body del request. Dado que estamos pasando los datos usando POST, la parte asociada al QueryString puede borrarse. Para leer los datos debería quedar algo como esto
dynamic data = await req.Content.ReadAsAsync<object>();
los valores que buscamos se pueden obtener así
string phone = data?.phone;
string email = data?.email;
también vamos a modificar ligeramente la respuesta, para validar que al menos uno de los valores sea enviado
return string.IsNullOrEmpty(phone) || string.IsNullOrEmpty(email)
? req.CreateResponse(HttpStatusCode.BadRequest, "Es necesario pasar teléfono o email")
: req.CreateResponse(HttpStatusCode.OK);
Ahora, ya que el proceso de obtener la frase puede tomar un tiempo largo y queremos asegurarnos de que todos los que la pidan la reciban, vamos a insertar las peticiones en un Queue. Para esto, creamos un nuevo output de tipo Azure Queue Storage en Integrate.
Especifica los nombres que quires usar y el valor de Storage Connection (que se lee de App Settings). Si el queue no existe aún, Azure Functions lo creará automáticamente cuando lo requiera.
Regresa a la sección Develop, y agrega el nuevo parámetro correspondiente al binding. Debe ser de tipo ICollector
y puede llamarse outputQueueItem; para insertar los valores en el queue quedaría
if(!string.IsNullOrEmpty(phone)){
outputQueueItem.Add($"{{'type': 'phone', 'value':'{phone}'}}");
}
if(!string.IsNullOrEmpty(email)){
outputQueueItem.Add($"{{'type': 'email', 'value':'{email}'}}");
}
Dado que estamos enviando un request de POST desde el front-end, podemos deshabilitar los otros verbos. En Integrate, selecciona el trigger y en la lista de Allowed HTTP methods selecciona Selected methods y en la sección Selected HTTP methods que aparece abajo a la derecha, selecciona solamente la casilla de POST.
Ahora necesitamos enlazar ambos triggers, para lo cual agregamos la URL de esta función al valor correspondiente de los App Settings que se describieron en el paso anterior. En Function App Settings (abajo-izquerda), click en Configure app settings. Desplaza hacia abajo, y agrega httptriggerurl a la lista
Procesando el queue
Ahora necesitamos procesar los mensajes que se van a insertar al queue. Para ello, ve a la sección Integrate nuevamente, y da clic en Azure Queue Storage output. Ve a Action, y da click en el botón Go que se encuentra al lado de la leyenda Create a new function triggered by this output.
Para cada mensaje en el queue, queremos enviarlo usando el método correspondiente, por lo que necesitamos agregar dos binding de salida a la función.
Ve a Integrate y agrega primero Twilio SMS output (necesitarás tener una cuenta de Twilio ya creada). Dado que Account SID, Auth Token y From number se leen de los App Settings podemos agregar el output así como esta; To number y Message serán agregados al ejecutar la función.
Ahora agregamos SendGrid output (es necesario tener una cuenta de SendGrid, que puede ser creada en Azure Portal Marketplace). De manera similar, API key se obtiene de App Settings, pero ahora hay que definir From address y Message Subject (que pueden también leerse de App Settings si fuera necesario). Message Text y To address se proveen en tiempo de ejecución.
Regresa a Develop, y agrega los bindings correspondientes a la firma del método
ICollector<SMSMessage> message, ICollector<Mail> email
Elimina el código actual de la función y reemplázalo con lo siguiente, que procesará cada elemento del queue, checando su tipo y enviando el mensaje correspondiente
dynamic queueItem = JsonConvert.DeserializeObject(myQueueItem);
string to = queueItem?.value;
if(queueItem?.type == "phone"){
log.Info($"Sending SMS to {to}");
SMSMessage sms = new SMSMessage();
sms.Body = fortune;
sms.To = $"+52{to}";
sms.From = ConfigurationManager.AppSettings["TwilioFrom"];
message.Add(sms);
}
if(queueItem?.type == "email"){
log.Info($"Sending Email to {to}");
Mail mail = new Mail();
Personalization personalization = new Personalization();
personalization.AddTo(new Email(to));
mail.AddPersonalization(personalization);
mail.AddContent(new Content("text/plain", fortune));
email.Add(mail);
}
fortune
se puede obtener de una base de datos o alguna otra fuente de storage. En este caso se definió una clase llamada FortuneCookieEngine que hará ese trabajo por nosotros.
FortuneCookieEngine f = new FortuneCookieEngine();
string fortune = f.Fortunes[new Random().Next(0, f.Fortunes.Length - 1)];
Ahora necesitamos agregar los valores correspondientes a App Setting para cada uno de los valores requeridos por los bindings, por lo cual deberías ver algo como esto
Resultado
Una vez completados todos los pasos, cuando se envíe un request desde la página del front-end debería recibirse algo como esto
Como puede verse, creamos una aplicación simple pero completa usando solamente Azure Functions. El código de lo creado para este post está en GitHub, en caso de que quieras descargarlo.[:en]One of the greatest advantages of working with Azure Functions is being able to prototype easily applications. For this post I will create a simple Fortune cookie app, which will send you a phrase to your email using Sendgrid and phone via SMS using Twilio. The app will be composed of three pieces
- Front-end web page using HttpTrigger
- POST request processing with HttpTrigger
- Queue processing using Azure Queue Storage Trigger
Front-end
We will use a HTTP trigger for our front-end. Since it will be just that, we don’t really need to create and upload a full web app that will contain only a single page.
So create a new function, of type HTTP Trigger
You will get asked for the name wich can be anithing you want. The important step here is to set the Authorization Level to Anonymous, since it will be public.
Once the function is created, as you can see, it’s already templated with many of the parts we need. Remove all the current code, except for the logging line. Since we want to return a different type of response, first we will create the response message object
HttpResponseMessage m = req.CreateResponse(HttpStatusCode.OK);
Notice we are assigning the Response
to a variable, instead of returning directly as it was before. This is because we want to modify the Content, declaring it will be of type text/html as follows
m.Content = new StringContent(sb.ToString(), Encoding.UTF8, "text/html");
sb will be a < code >StringBuilder object that will help creating the HTML we want to return. We might be able to create it also with HtmlTextWriter, but for this example I prefer to have a string that gives a better visual cue.
On line 40, I’m using string interpolation to provide the value corresponding to the URL that will actually process my requests. We will get the value from the FunctionApp Settings,
string httptriggerurl = ConfigurationManager.AppSettings["httptriggerurl"];
This completes our front-end
Processing the requests
Create a new function of type HTTP Trigger, and this time set the authorization level value to Function. The function (again) contains some templated code. It is desgined to read data from the query string or the request body. Since we are passing the data using a POST, the querystring part can be removed. Your data reading should be something like this
dynamic data = await req.Content.ReadAsAsync<object>();
our desired values can be retrieved as
string phone = data?.phone;
string email = data?.email;
we will also tweak the response to validate that we get at least one of the values
return string.IsNullOrEmpty(phone) || string.IsNullOrEmpty(email)
? req.CreateResponse(HttpStatusCode.BadRequest, "Es necesario pasar teléfono o email")
: req.CreateResponse(HttpStatusCode.OK);
Now, since the process of getting your fortune can take a long time to complete and we want to ensure everybody actually gets it, we will insert the received requests into an queue. For this, create a new output of type Azure Queue Storage in the Integrate section.
Specify the names you want to use and the Storage Connection (which will be read from the App Settings). If the queue doesn’t exist yet, Azure Functions will create it for you automatically.
Go back to develop section and add a new binding parameter of type ICollector named outputQueueItem, and for inserting the values into the queue just add
if(!string.IsNullOrEmpty(phone)){
outputQueueItem.Add($"{{'type': 'phone', 'value':'{phone}'}}");
}
if(!string.IsNullOrEmpty(email)){
outputQueueItem.Add($"{{'type': 'email', 'value':'{email}'}}");
}
Since we are sending a POST request from the front-end function, we can disallow the other verbs. Go to Integrate section, select the trigger and in the Allowed HTTP methods list pick Selected methods and in the Selected HTTP methods section that will appear on the bottom-right, uncheck everything except POST.
Now, we need to link both triggers together by adding the URL of this function to the corresponding App Setting we described in the previous step. Go to Function App Settings on the bottom-left of your screen, and click on Configure app settings button. Scroll down to the App settings section and add httptriggerurl to the list
Processing the queue
We now need to process the messages that have been inserted into the queue. For this, go to the Integrate section again, and click on the Azure Queue Storage output. Scroll down to Action section, and click Go button beside Create a new function triggered by this output legend.
For each message in the queue, we want to deliver it using the corresponding method, so we need to add two outputs for the function.
Go to Integrate and first add the Twilio SMS output (you will need to have a Twilio account already created). Since Account SID, Auth Token and From number values will be read from the app settings we can just add the output as is, since To number and the Message values will be provided at runtime.
Next we need to add the SendGrid output (you will need to have a SendGrid account, that can be created from Azure Portal Marketplace). Similar case, the API key will be retrieved from App Settings, but now you need to define the From address and Message Subject (you can read them from App Settings also if required). Message Text and To address values will be set at runtime.
Go back to Develop, and add the corresponding bindings at the end of your function signature
ICollector<SMSMessage> message, ICollector<Mail> email
Remove the current function code, and replace it with the following. We will be processing each queue item, check the type and send the corresponding message.
dynamic queueItem = JsonConvert.DeserializeObject(myQueueItem);
string to = queueItem?.value;
if(queueItem?.type == "phone"){
log.Info($"Sending SMS to {to}");
SMSMessage sms = new SMSMessage();
sms.Body = fortune;
sms.To = $"+52{to}";
sms.From = ConfigurationManager.AppSettings["TwilioFrom"];
message.Add(sms);
}
if(queueItem?.type == "email"){
log.Info($"Sending Email to {to}");
Mail mail = new Mail();
Personalization personalization = new Personalization();
personalization.AddTo(new Email(to));
mail.AddPersonalization(personalization);
mail.AddContent(new Content("text/plain", fortune));
email.Add(mail);
}
fortune
value can be retrieved from a database or a different storage source. In this case I defined a class called FortuneCookieEngine that will do that work for us.
FortuneCookieEngine f = new FortuneCookieEngine();
string fortune = f.Fortunes[new Random().Next(0, f.Fortunes.Length - 1)];
Now we need to add the corresponding App Setting values for each of the settings we need for the outputs, so you will end up with something like this
Result
Once you complete all the steps, when sending a request from front-end page you should receive something like this
As you can see we created a simple yet somehow full application using only Azure Functions.
Code from this post it is on GitHub, in case you want to download it.[:]
Leave a Reply