# 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.](https://customers.twilio.com/214/airbnb/)*

## Workflow Building Blocks

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

* [Sending Messages with Twilio API](/docs/messaging/quickstart)

```cs title="Send messages"
// !mark(14:32)
using System.Threading.Tasks;
using AirTNG.Web.Domain.Twilio;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;

namespace AirTNG.Web.Domain.Reservations
{
    public interface ITwilioMessageSender
    {
        Task SendMessageAsync(string to, string body);
    }

    public class TwilioMessageSender : ITwilioMessageSender
    {

        private readonly TwilioConfiguration _configuration;

        public TwilioMessageSender(TwilioConfiguration configuration)
        {
            _configuration = configuration;

            TwilioClient.Init(_configuration.AccountSid, _configuration.AuthToken);
        }

        public async Task SendMessageAsync(string to, string body)
        {
            await MessageResource.CreateAsync(new PhoneNumber(to),
                                              from: new PhoneNumber(_configuration.PhoneNumber),
                                              body: body);
        }
    }
}
```

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

## Authenticate Users

For this use case to work we have to handle authentication. We will rely on [*ASP.NET Core Identity*](https://www.asp.net/identity/overview/getting-started/introduction-to-aspnet-identity) for this purpose.

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

```cs title="The User model"
// !mark(6:11)
using System;
using Microsoft.AspNetCore.Identity;

namespace AirTNG.Web.Models
{
    public class ApplicationUser:IdentityUser
    {

        public string Name { get; set; }

    }
}
```

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

## The Vacation Property Model

Our rental application will 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.`

```cs title="Vacation Property Model"
// !mark(8:20)
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Identity;

namespace AirTNG.Web.Models
{
    public class VacationProperty
    {
        public int Id { get; set; }
        public string UserId { get; set; }
        public string Description { get; set; }
        public string ImageUrl { get; set; }
        public DateTime CreatedAt { get; set; }

        [ForeignKey("UserId")]
        public ApplicationUser User { get; set; }

        public virtual IList<Reservation> Reservations { get; set; }
    }
}
```

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

## Reservation Model

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.

```cs title="The Reservation Model"
// !mark(6:18)
using System;
using System.ComponentModel.DataAnnotations.Schema;

namespace AirTNG.Web.Models
{
    public class Reservation
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string PhoneNumber { get; set; }
        public ReservationStatus Status { get; set; }
        public string Message { get; set; }
        public DateTime CreatedAt { get; set; }
        public int VacationPropertyId { get; set; }

        [ForeignKey("VacationPropertyId")]
        public VacationProperty VacationProperty { get; set; }
    }

    public enum ReservationStatus
    {
        Pending = 0,
        Confirmed = 1,
        Rejected = 2
    }
}
```

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

## Create a Reservation

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.

```cs title="The Reservation Controller"
// !mark(50:79)
using System;
using System.Threading.Tasks;
using AirTNG.Web.Data;
using AirTNG.Web.Domain.Reservations;
using Microsoft.AspNetCore.Mvc;
using AirTNG.Web.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;

namespace AirTNG.Web.Tests.Controllers
{
    [Authorize]
    public class ReservationController : Controller
    {
        private readonly IApplicationDbRepository _repository;
        private readonly IUserRepository _userRepository;
        private readonly INotifier _notifier;

        public ReservationController(
            IApplicationDbRepository applicationDbRepository,
            IUserRepository userRepository,
            INotifier notifier)
        {
            _repository = applicationDbRepository;
            _userRepository = userRepository;
            _notifier = notifier;
        }

        // GET: Reservation/Create/1
        public async Task<IActionResult> Create(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
            var property = await _repository.FindVacationPropertyFirstOrDefaultAsync(id);
            if (property == null)
            {
                return NotFound();
            }

            ViewData["VacationProperty"] = property;
            return View();
        }

        // POST: Reservation/Create/1
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create(int id, [Bind("Message,VacationPropertyId")] Reservation reservation)
        {
            if (id != reservation.VacationPropertyId)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                var user = await _userRepository.GetUserAsync(HttpContext.User);
                reservation.Status = ReservationStatus.Pending;
                reservation.Name = user.Name;
                reservation.PhoneNumber = user.PhoneNumber;
                reservation.CreatedAt = DateTime.Now;

                await _repository.CreateReservationAsync(reservation);
                var notification = Notification.BuildHostNotification(
                    await _repository.FindReservationWithRelationsAsync(reservation.Id));

                await _notifier.SendNotificationAsync(notification);

                return RedirectToAction("Index", "VacationProperty");
            }

            ViewData["VacationProperty"] = await _repository.FindVacationPropertyFirstOrDefaultAsync(
                reservation.VacationPropertyId);
            return View(reservation);
        }

    }
}
```

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

## Notify The 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# SDK](https://github.com/twilio/twilio-csharp) to send a SMS message to the *host*, using our [Twilio phone number](https://twilio.com/console). 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.

```cs title="Notify the host"
// !mark(18:35)
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AirTNG.Web.Domain.Twilio;
using AirTNG.Web.Models;
using Twilio;
using Twilio.Clients;
using Twilio.TwiML.Messaging;

namespace AirTNG.Web.Domain.Reservations
{
    public interface INotifier
    {
        Task SendNotificationAsync(Notification notification);
    }

    public class Notifier : INotifier
    {
        private readonly ITwilioMessageSender _client;

        public Notifier(TwilioConfiguration configuration) : this(
            new TwilioMessageSender(configuration)
        ) { }

        public Notifier(ITwilioMessageSender client)
        {
            _client = client;
        }

        public async Task SendNotificationAsync(Notification notification)
        {
            await _client.SendMessageAsync(notification.To, notification.Message);
        }
    }
}
```

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

## Handle Incoming Messages

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

```cs title="Handle a host response"
// !mark(32:68)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AirTNG.Web.Data;
using AirTNG.Web.Domain.Reservations;
using AirTNG.Web.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Twilio.AspNet.Core;
using Twilio.TwiML;
using Twilio.TwiML.Voice;

namespace AirTNG.Web.Tests.Controllers
{
    public class SmsController: TwilioController
    {
        private readonly IApplicationDbRepository _repository;
        private readonly INotifier _notifier;

        public SmsController(
            IApplicationDbRepository repository,
            INotifier notifier)
        {
            _repository = repository;
            _notifier = notifier;
        }


        // POST Sms/Handle
        [HttpPost]
        [AllowAnonymous]
        public async Task<TwiMLResult> Handle(string from, string body)
        {
            string smsResponse;

            try
            {
                var host = await _repository.FindUserByPhoneNumberAsync(from);
                var reservation = await _repository.FindFirstPendingReservationByHostAsync(host.Id);

                var smsRequest = body;
                reservation.Status =
                    smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
                    smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)
                        ? ReservationStatus.Confirmed
                        : ReservationStatus.Rejected;

                await _repository.UpdateReservationAsync(reservation);
                smsResponse = $"You have successfully {reservation.Status} the reservation";

                // Notify guest with host response
                var notification = Notification.BuildGuestNotification(reservation);

                await _notifier.SendNotificationAsync(notification);
            }
            catch (InvalidOperationException)
            {
                smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
            }
            catch (Exception)
            {
                smsResponse = "Sorry, it looks like we get an error. Try later!";
            }

            return TwiML(Respond(smsResponse));
        }

        private static MessagingResponse Respond(string message)
        {
            var response = new MessagingResponse();
            response.Message(message);

            return response;
        }
    }
}
```

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

## Incoming Twilio Requests

In the [Twilio console](https://twilio.com/console), you must setup the SMS webhook to call your application's end point in the route `Reservations/Handle`.

![Messaging configuration with webhook URL set to https://yourserver.com/sms.](https://docs-resources.prod.twilio.com/f25966b33e960a4214a186c1dbdd0d35aff94f3b3c704f7db85ba8a082ec20d2.png)

One way to expose your development machine to the outside world is using [ngrok](https://ngrok.com/). The URL for the SMS webhook on your number would look like this:

```bash
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 they have any pending reservations. If they do, we'll use the message body to check for 'accept' and 'reject'.

In the last step, we'll use [Twilio's TwiML](/docs/glossary/what-is-twilio-markup-language-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.

## TwiML Response

After updating the reservation status, we must notify the *host* that they have 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](/docs/messaging/twiml/message) from TwiML to instruct Twilio's server that it should send the SMS messages.

```cs title="Respond to a message"
// !mark(70:76)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AirTNG.Web.Data;
using AirTNG.Web.Domain.Reservations;
using AirTNG.Web.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Twilio.AspNet.Core;
using Twilio.TwiML;
using Twilio.TwiML.Voice;

namespace AirTNG.Web.Tests.Controllers
{
    public class SmsController: TwilioController
    {
        private readonly IApplicationDbRepository _repository;
        private readonly INotifier _notifier;

        public SmsController(
            IApplicationDbRepository repository,
            INotifier notifier)
        {
            _repository = repository;
            _notifier = notifier;
        }


        // POST Sms/Handle
        [HttpPost]
        [AllowAnonymous]
        public async Task<TwiMLResult> Handle(string from, string body)
        {
            string smsResponse;

            try
            {
                var host = await _repository.FindUserByPhoneNumberAsync(from);
                var reservation = await _repository.FindFirstPendingReservationByHostAsync(host.Id);

                var smsRequest = body;
                reservation.Status =
                    smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
                    smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase)
                        ? ReservationStatus.Confirmed
                        : ReservationStatus.Rejected;

                await _repository.UpdateReservationAsync(reservation);
                smsResponse = $"You have successfully {reservation.Status} the reservation";

                // Notify guest with host response
                var notification = Notification.BuildGuestNotification(reservation);

                await _notifier.SendNotificationAsync(notification);
            }
            catch (InvalidOperationException)
            {
                smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
            }
            catch (Exception)
            {
                smsResponse = "Sorry, it looks like we get an error. Try later!";
            }

            return TwiML(Respond(smsResponse));
        }

        private static MessagingResponse Respond(string message)
        {
            var response = new MessagingResponse();
            response.Message(message);

            return response;
        }
    }
}
```

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.

## Where to Next?

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](/docs/voice/tutorials/build-interactive-voice-response-ivr-phone-tree/csharp)**

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

**[Automated Survey](/blog/automated-survey-csharp-aspnet-mvc)**

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