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.