Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Workflow Automation with C# and ASP.NET Core


One of the more abstract concepts you'll handle when building your business is what the workflow will look like.

At its core, setting up a standardized workflow is about enabling your service providers (agents, hosts, customer service reps, administrators, and the rest of the gang) to better serve your customers.

To illustrate a very real-world example, today we'll build a C# and ASP.NET Core MVC webapp for finding and booking vacation properties — tentatively called Airtng.

Here's how it'll work:

  1. A host creates a vacation property listing
  2. A guest requests a reservation for a property
  3. The host receives an SMS notifying them of the reservation request. The host can either Accept or Reject the reservation
  4. The guest is notified whether a request was rejected or accepted

Learn how Airbnb used Twilio SMS to streamline the rental experience for 60M+ travelers around the world.(link takes you to an external page)


Workflow Building Blocks

workflow-building-blocks page anchor

We'll be using the Twilio REST API to send our users messages at important junctures. Here's a bit more on our API:

Send messages

send-messages page anchor

_33
using System.Threading.Tasks;
_33
using AirTNG.Web.Domain.Twilio;
_33
using Twilio;
_33
using Twilio.Rest.Api.V2010.Account;
_33
using Twilio.Types;
_33
_33
namespace AirTNG.Web.Domain.Reservations
_33
{
_33
public interface ITwilioMessageSender
_33
{
_33
Task SendMessageAsync(string to, string body);
_33
}
_33
_33
public class TwilioMessageSender : ITwilioMessageSender
_33
{
_33
_33
private readonly TwilioConfiguration _configuration;
_33
_33
public TwilioMessageSender(TwilioConfiguration configuration)
_33
{
_33
_configuration = configuration;
_33
_33
TwilioClient.Init(_configuration.AccountSid, _configuration.AuthToken);
_33
}
_33
_33
public async Task SendMessageAsync(string to, string body)
_33
{
_33
await MessageResource.CreateAsync(new PhoneNumber(to),
_33
from: new PhoneNumber(_configuration.PhoneNumber),
_33
body: body);
_33
}
_33
}
_33
}

Ready to go? Boldly click the button right after this sentence.


For this use case to work we have to handle authentication. We will rely on ASP.NET Core Identity(link takes you to an external page) for this purpose.

Identity User already includes a phone_number that will be required to later send SMS notifications.


_12
using System;
_12
using Microsoft.AspNetCore.Identity;
_12
_12
namespace AirTNG.Web.Models
_12
{
_12
public class ApplicationUser:IdentityUser
_12
{
_12
_12
public string Name { get; set; }
_12
_12
}
_12
}

Next let's take a look at the Vacation Property model.


The Vacation Property Model

the-vacation-property-model page anchor

Our rental application will obviously require listing properties.

The VacationProperty belongs to the User who created it (we'll call this user the host from this point on) and contains only two properties: a Description and an ImageUrl.

A VacationProperty can have many Reservations.


_21
using System;
_21
using System.Collections.Generic;
_21
using System.ComponentModel.DataAnnotations.Schema;
_21
using Microsoft.AspNetCore.Identity;
_21
_21
namespace AirTNG.Web.Models
_21
{
_21
public class VacationProperty
_21
{
_21
public int Id { get; set; }
_21
public string UserId { get; set; }
_21
public string Description { get; set; }
_21
public string ImageUrl { get; set; }
_21
public DateTime CreatedAt { get; set; }
_21
_21
[ForeignKey("UserId")]
_21
public ApplicationUser User { get; set; }
_21
_21
public virtual IList<Reservation> Reservations { get; set; }
_21
}
_21
}

Next, let's see what our Reservation model looks like.


The Reservation model is at the center of the workflow for this application.

It is responsible for keeping track of:

  • The VacationProperty it is associated with to have access. Through this property the user will have access to the host phone number indirectly.
  • The Name and PhoneNumber of the guest.
  • The Message sent to the host on reservation.
  • The Status of the reservation.

_26
using System;
_26
using System.ComponentModel.DataAnnotations.Schema;
_26
_26
namespace AirTNG.Web.Models
_26
{
_26
public class Reservation
_26
{
_26
public int Id { get; set; }
_26
public string Name { get; set; }
_26
public string PhoneNumber { get; set; }
_26
public ReservationStatus Status { get; set; }
_26
public string Message { get; set; }
_26
public DateTime CreatedAt { get; set; }
_26
public int VacationPropertyId { get; set; }
_26
_26
[ForeignKey("VacationPropertyId")]
_26
public VacationProperty VacationProperty { get; set; }
_26
}
_26
_26
public enum ReservationStatus
_26
{
_26
Pending = 0,
_26
Confirmed = 1,
_26
Rejected = 2
_26
}
_26
}

Now that our models are ready, let's have a look at the controller that will create reservations.


The reservation creation form holds only a single field: the message that will be sent to the host when one of her properties is reserved. The rest of the information needed to create a reservation is taken from the VacationProperty itself.

A reservation is created with a default status ReservationStatus.Pending. That way when the host replies with an accept or reject response the application knows which reservation to update.

The Reservation Controller

the-reservation-controller page anchor

_82
using System;
_82
using System.Threading.Tasks;
_82
using AirTNG.Web.Data;
_82
using AirTNG.Web.Domain.Reservations;
_82
using Microsoft.AspNetCore.Mvc;
_82
using AirTNG.Web.Models;
_82
using Microsoft.AspNetCore.Authorization;
_82
using Microsoft.AspNetCore.Identity;
_82
using Microsoft.Extensions.Logging;
_82
_82
namespace AirTNG.Web.Tests.Controllers
_82
{
_82
[Authorize]
_82
public class ReservationController : Controller
_82
{
_82
private readonly IApplicationDbRepository _repository;
_82
private readonly IUserRepository _userRepository;
_82
private readonly INotifier _notifier;
_82
_82
public ReservationController(
_82
IApplicationDbRepository applicationDbRepository,
_82
IUserRepository userRepository,
_82
INotifier notifier)
_82
{
_82
_repository = applicationDbRepository;
_82
_userRepository = userRepository;
_82
_notifier = notifier;
_82
}
_82
_82
// GET: Reservation/Create/1
_82
public async Task<IActionResult> Create(int? id)
_82
{
_82
if (id == null)
_82
{
_82
return NotFound();
_82
}
_82
var property = await _repository.FindVacationPropertyFirstOrDefaultAsync(id);
_82
if (property == null)
_82
{
_82
return NotFound();
_82
}
_82
_82
ViewData["VacationProperty"] = property;
_82
return View();
_82
}
_82
_82
// POST: Reservation/Create/1
_82
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
_82
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
_82
[HttpPost]
_82
[ValidateAntiForgeryToken]
_82
public async Task<IActionResult> Create(int id, [Bind("Message,VacationPropertyId")] Reservation reservation)
_82
{
_82
if (id != reservation.VacationPropertyId)
_82
{
_82
return NotFound();
_82
}
_82
_82
if (ModelState.IsValid)
_82
{
_82
var user = await _userRepository.GetUserAsync(HttpContext.User);
_82
reservation.Status = ReservationStatus.Pending;
_82
reservation.Name = user.Name;
_82
reservation.PhoneNumber = user.PhoneNumber;
_82
reservation.CreatedAt = DateTime.Now;
_82
_82
await _repository.CreateReservationAsync(reservation);
_82
var notification = Notification.BuildHostNotification(
_82
await _repository.FindReservationWithRelationsAsync(reservation.Id));
_82
_82
await _notifier.SendNotificationAsync(notification);
_82
_82
return RedirectToAction("Index", "VacationProperty");
_82
}
_82
_82
ViewData["VacationProperty"] = await _repository.FindVacationPropertyFirstOrDefaultAsync(
_82
reservation.VacationPropertyId);
_82
return View(reservation);
_82
}
_82
_82
}
_82
}

Next, let's see how we will send SMS notifications to the vacation rental host.


When a reservation is created we want to notify the owner of the property that someone is interested.

This is where we use Twilio C# Helper Library(link takes you to an external page) to send a SMS message to the host, using our Twilio phone number(link takes you to an external page). As you can see, sending SMS messages using Twilio takes just a few lines of code.

Next we just have to wait for the host to send an SMS response accepting or rejecting the reservation. Then we can notify the guest and host that the reservation information has been updated.


_36
using System;
_36
using System.Linq;
_36
using System.Text;
_36
using System.Threading.Tasks;
_36
using AirTNG.Web.Domain.Twilio;
_36
using AirTNG.Web.Models;
_36
using Twilio;
_36
using Twilio.Clients;
_36
using Twilio.TwiML.Messaging;
_36
_36
namespace AirTNG.Web.Domain.Reservations
_36
{
_36
public interface INotifier
_36
{
_36
Task SendNotificationAsync(Notification notification);
_36
}
_36
_36
public class Notifier : INotifier
_36
{
_36
private readonly ITwilioMessageSender _client;
_36
_36
public Notifier(TwilioConfiguration configuration) : this(
_36
new TwilioMessageSender(configuration)
_36
) { }
_36
_36
public Notifier(ITwilioMessageSender client)
_36
{
_36
_client = client;
_36
}
_36
_36
public async Task SendNotificationAsync(Notification notification)
_36
{
_36
await _client.SendMessageAsync(notification.To, notification.Message);
_36
}
_36
}
_36
}

Now's let's peek at how we're handling the host's responses.


Handle Incoming Messages

handle-incoming-messages page anchor

The Sms/Handle controller handles our incoming Twilio request and does four things:

  1. Check for the guest's pending reservation
  2. Update the status of the reservation
  3. Respond to the host
  4. Send notification to the guest

_78
using System;
_78
using System.Collections.Generic;
_78
using System.Linq;
_78
using System.Threading.Tasks;
_78
using AirTNG.Web.Data;
_78
using AirTNG.Web.Domain.Reservations;
_78
using AirTNG.Web.Models;
_78
using Microsoft.AspNetCore.Authorization;
_78
using Microsoft.AspNetCore.Mvc;
_78
using Microsoft.EntityFrameworkCore;
_78
using Twilio.AspNet.Core;
_78
using Twilio.TwiML;
_78
using Twilio.TwiML.Voice;
_78
_78
namespace AirTNG.Web.Tests.Controllers
_78
{
_78
public class SmsController: TwilioController
_78
{
_78
private readonly IApplicationDbRepository _repository;
_78
private readonly INotifier _notifier;
_78
_78
public SmsController(
_78
IApplicationDbRepository repository,
_78
INotifier notifier)
_78
{
_78
_repository = repository;
_78
_notifier = notifier;
_78
}
_78
_78
_78
// POST Sms/Handle
_78
[HttpPost]
_78
[AllowAnonymous]
_78
public async Task<TwiMLResult> Handle(string from, string body)
_78
{
_78
string smsResponse;
_78
_78
try
_78
{
_78
var host = await _repository.FindUserByPhoneNumberAsync(from);
_78
var reservation = await _repository.FindFirstPendingReservationByHostAsync(host.Id);
_78
_78
var smsRequest = body;
_78
reservation.Status =
_78
smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
_78
smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)
_78
? ReservationStatus.Confirmed
_78
: ReservationStatus.Rejected;
_78
_78
await _repository.UpdateReservationAsync(reservation);
_78
smsResponse = $"You have successfully {reservation.Status} the reservation";
_78
_78
// Notify guest with host response
_78
var notification = Notification.BuildGuestNotification(reservation);
_78
_78
await _notifier.SendNotificationAsync(notification);
_78
}
_78
catch (InvalidOperationException)
_78
{
_78
smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
_78
}
_78
catch (Exception)
_78
{
_78
smsResponse = "Sorry, it looks like we get an error. Try later!";
_78
}
_78
_78
return TwiML(Respond(smsResponse));
_78
}
_78
_78
private static MessagingResponse Respond(string message)
_78
{
_78
var response = new MessagingResponse();
_78
response.Message(message);
_78
_78
return response;
_78
}
_78
}
_78
}

Let's have closer look at how Twilio webhooks are configured to enable incoming requests to our application.


Incoming Twilio Requests

incoming-twilio-requests page anchor

In the Twilio console(link takes you to an external page), you must setup the sms web hook to call your application's end point in the route Reservations/Handle.

SMS Webhook.

One way to expose your development machine to the outside world is using ngrok(link takes you to an external page). The url for the sms web hook on your number would look like this:


_10
http://<subdomain>.ngrok.io/Reservations/Handle

An incoming request from Twilio comes with some helpful parameters, such as a from phone number and the message body.

We'll use the from parameter to look for the host and check if he/she has any pending reservations. If he/she does, we'll use the message body to check for 'accept' and 'reject'.

In the last step, we'll use Twilio's TwiML as a response to Twilio to send an SMS message to the guest.

Now that we know how to expose a webhook to handle Twilio requests, let's see how we generate the TwiML needed.


After updating the reservation status, we must notify the host that he/she has successfully confirmed or rejected the reservation. We also have to return a friendly error message if there are no pending reservations.

If the reservation is confirmed or rejected we send an additional SMS to the guest to deliver the news.

We use the verb Message from TwiML to instruct Twilio's server that it should send the SMS messages.


_78
using System;
_78
using System.Collections.Generic;
_78
using System.Linq;
_78
using System.Threading.Tasks;
_78
using AirTNG.Web.Data;
_78
using AirTNG.Web.Domain.Reservations;
_78
using AirTNG.Web.Models;
_78
using Microsoft.AspNetCore.Authorization;
_78
using Microsoft.AspNetCore.Mvc;
_78
using Microsoft.EntityFrameworkCore;
_78
using Twilio.AspNet.Core;
_78
using Twilio.TwiML;
_78
using Twilio.TwiML.Voice;
_78
_78
namespace AirTNG.Web.Tests.Controllers
_78
{
_78
public class SmsController: TwilioController
_78
{
_78
private readonly IApplicationDbRepository _repository;
_78
private readonly INotifier _notifier;
_78
_78
public SmsController(
_78
IApplicationDbRepository repository,
_78
INotifier notifier)
_78
{
_78
_repository = repository;
_78
_notifier = notifier;
_78
}
_78
_78
_78
// POST Sms/Handle
_78
[HttpPost]
_78
[AllowAnonymous]
_78
public async Task<TwiMLResult> Handle(string from, string body)
_78
{
_78
string smsResponse;
_78
_78
try
_78
{
_78
var host = await _repository.FindUserByPhoneNumberAsync(from);
_78
var reservation = await _repository.FindFirstPendingReservationByHostAsync(host.Id);
_78
_78
var smsRequest = body;
_78
reservation.Status =
_78
smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
_78
smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)
_78
? ReservationStatus.Confirmed
_78
: ReservationStatus.Rejected;
_78
_78
await _repository.UpdateReservationAsync(reservation);
_78
smsResponse = $"You have successfully {reservation.Status} the reservation";
_78
_78
// Notify guest with host response
_78
var notification = Notification.BuildGuestNotification(reservation);
_78
_78
await _notifier.SendNotificationAsync(notification);
_78
}
_78
catch (InvalidOperationException)
_78
{
_78
smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
_78
}
_78
catch (Exception)
_78
{
_78
smsResponse = "Sorry, it looks like we get an error. Try later!";
_78
}
_78
_78
return TwiML(Respond(smsResponse));
_78
}
_78
_78
private static MessagingResponse Respond(string message)
_78
{
_78
var response = new MessagingResponse();
_78
response.Message(message);
_78
_78
return response;
_78
}
_78
}
_78
}

Congratulations! You've just learned how to automate your workflow with Twilio SMS.

Next, lets see what else we can do with the Twilio C# SDK.


If you're a .NET developer working with Twilio you know we've got a lot of great content here on the Docs site. Here are just a couple ideas for your next tutorial:

IVR: Phone Tree

Easily route callers to the right people and information with an IVR (interactive voice response) system.

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

Did this help?

did-this-help page anchor

Thanks for checking out this tutorial! Tweet to us @twilio(link takes you to an external page) with what you're building!


Rate this page: