# Send Appointment Reminders with C# and ASP.NET MVC

> \[!NOTE]
>
> Ahoy! We now recommend you build your appointment reminders with Twilio's built-in Message Scheduling functionality. Head on over to the [Message Scheduling documentation](/docs/messaging/features/message-scheduling) to learn more about scheduling messages.

Ready to implement appointment reminders in your application? Here's how it works:

1. An administrator creates an appointment for a future date and time, and stores a customer's phone number in the database for that appointment
2. A background process checks the database on a regular interval, looking for appointments that require a reminder to be sent out
3. At a configured time in advance of the appointment, an SMS reminder is sent out to the customer to remind them of their appointment

*[Check out how Yelp uses SMS to confirm restaurant reservations for diners.](https://customers.twilio.com/1154/yelp/?utm_source=docs\&utm_campaign=docs_to_stories)*

## Building Blocks

Here are the technologies we'll use to get this done:

* [ASP.NET MVC](https://www.asp.net/mvc) to create a database-driven web application
* [The Messages Resource](/docs/messaging/quickstart) from Twilio's REST API to send text messages
* [Hangfire](https://hangfire.io/) to help us schedule and execute background tasks on a recurring basis

In this tutorial, we'll point out key snippets of code that make this application work. Check out the [project README on GitHub](https://github.com/TwilioDevEd/appointment-reminders-csharp) to see how to run the code yourself.

## How To Read This Tutorial

To implement appointment reminders, we will be working through a series of [user stories](https://en.wikipedia.org/wiki/User_story) that describe how to fully implement appointment reminders in a web application. We'll walk through the code required to satisfy each story, and explore what we needed to add on each step.

All this can be done with the help of Twilio in under half an hour.

## Creating an Appointment

> As a user, I want to create an appointment with a name, guest phone numbers, and a time in the future.

In order to build an automated appointment reminder application, we probably should start with an appointment. This story requires that we create a bit of UI and a model object to create and save a new `Appointment` in our system. At a high level, here's what we will need to add:

* A form to enter details about the appointment
* A route and controller function on the server to render the form
* A route and controller function on the server to handle the form `POST` request
* A persistent `Appointment` model object to store information about the user

Alright, so we know what we need to create a new appointment. Now let's start by looking at the model, where we decide what information we want to store with the appointment.

## Appointment Model

The appointment model is fairly straightforward, but since humans will be interacting with it let's make sure we add some data validation.

Our application relies on [ASP.NET Data Annotations](https://www.asp.net/mvc/overview/older-versions/mvc-music-store/mvc-music-store-part-6). In our case, we only want to validate that some fields are required. To accomplish this we'll use `[Required]` data annotation.

By default, ASP.NET MVC displays the property name when rendering a control. In our example those property names can be `Name` or `PhoneNumber`. For rendering `Name` there shouldn't be any problem. But for `PhoneNumber` we might want to display something nicer, like "Phone Number". For this kind of scenario we can use another data annotation: `[Display(Name = "Phone Number")]`.

For validating the contents of the `PhoneNumber` field, we're using `[Phone]` data annotation, which confirms user-entered phone numbers conform loosely to [E.164 formatting standards](https://en.wikipedia.org/wiki/E.164).

```cs title="The Appointment Model" description="AppointmentReminders.Web/Models/Appointment.cs"
// !mark(7:26)
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.Ajax.Utilities;

namespace AppointmentReminders.Web.Models
{
    public class Appointment
    {
        public static int ReminderTime = 30;
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        [Required, Phone, Display(Name = "Phone number")]
        public string PhoneNumber { get; set; }

        [Required]
        public DateTime Time { get; set; }

        [Required]
        public string Timezone { get; set; }

        [Display(Name = "Created at")]
        public DateTime CreatedAt { get; set; }
    }
}
```

Our appointment model is now defined. It's time to take a look at the form that allows an administrator to create new appointments.

## New Appointment Form

When we create a new appointment, we need a guest name, a phone number and a time. By using [HTML Helper classes](https://msdn.microsoft.com/en-us/library/system.web.mvc.htmlhelper.aspx) we can bind the form to the model object. Those helpers will generate the necessary HTML markup that will create a new appointment on submit.

```razor title="New Appointment Form"  description="AppointmentReminders.Web/Views/Appointments/_Form.cshtml"
@using AppointmentReminders.Web.Extensions
@model AppointmentReminders.Web.Models.Appointment

<div class="form-group">
    @Html.LabelFor(x => x.Name, new { @class = "control-label col-lg-2" })
    <div class="col-lg-10">
        @Html.TextBoxFor(x => x.Name, new { @class = "form-control", @required = "required" })
    </div>
</div>
<div class="form-group">
    @Html.LabelFor(x => x.PhoneNumber, new { @class = "control-label col-lg-2" })
    <div class="col-lg-10">
        @Html.TextBoxFor(x => x.PhoneNumber, new { @class = "form-control", @required = "required" })
    </div>
</div>
<div class="form-group">
    <label class="control-label col-lg-2">Time and Date</label>
    <div class="col-lg-10">
        <div class="row">
            <div class="col-lg-6">
                <div class='input-group date' id="datetimepicker">
                    @Html.TextBox("Time", Model.Time.ToCustomDateString(), new { @class = "form-control"})
                    <span class="input-group-addon">
                        <span class="glyphicon glyphicon-calendar"></span>
                    </span>
                </div>
            </div>
            <div class="col-lg-6 pull-right">
                @Html.DropDownListFor(x => x.Timezone, (IEnumerable<SelectListItem>)ViewBag.Timezones, new { @class = "form-control" })
            </div>
        </div>
    </div>
</div>
```

Now that we have a model and a UI, we will see how we can interact with `Appointments`.

## Interacting with Appointments

> As a user, I want to view a list of all future appointments, and be able to delete those appointments.

If you're an organization that handles a lot of appointments, you probably want to be able to view and manage them in a single interface. That's what we'll tackle in this user story. We'll create a UI to:

* Show all appointments
* Delete individual appointments

We know what interactions we want to implement, so let's look first at how to list all upcoming **Appointments**.

## Getting a List of Appointments

At the controller level, we'll get a list of all the appointments in the database and render them with a view. We also add a prompt if there aren't any appointments, so the admin user can create one.

```cs title="List all Appointments" description="AppointmentReminders.Web/Controllers/AppointmentsController.cs"
// !mark(34:40)
using System;
using System.Linq;
using System.Net;
using System.Web.Mvc;
using AppointmentReminders.Web.Models;
using AppointmentReminders.Web.Models.Repository;

namespace AppointmentReminders.Web.Controllers
{
    public class AppointmentsController : Controller
    {
        private readonly IAppointmentRepository _repository;

        public AppointmentsController() : this(new AppointmentRepository()) { }

        public AppointmentsController(IAppointmentRepository repository)
        {
            _repository = repository;
        }

        public SelectListItem[] Timezones
        {
            get
            {
                var systemTimeZones = TimeZoneInfo.GetSystemTimeZones();
                return systemTimeZones.Select(systemTimeZone => new SelectListItem
                {
                    Text = systemTimeZone.DisplayName,
                    Value = systemTimeZone.Id
                }).ToArray();
            }
        }

        // GET: Appointments
        public ActionResult Index()
        {
            var appointments = _repository.FindAll();
            return View(appointments);
        }

        // GET: Appointments/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            var appointment = _repository.FindById(id.Value);
            if (appointment == null)
            {
                return HttpNotFound();
            }

            return View(appointment);
        }

        // GET: Appointments/Create
        public ActionResult Create()
        {
            ViewBag.Timezones = Timezones;
            // Use an empty appointment to setup the default
            // values.
            var appointment = new Appointment
            {
                Timezone = "Pacific Standard Time",
                Time = DateTime.Now
            };

            return View(appointment);
        }

        [HttpPost]
        public ActionResult Create([Bind(Include="ID,Name,PhoneNumber,Time,Timezone")]Appointment appointment)
        {
            appointment.CreatedAt = DateTime.Now;

            if (ModelState.IsValid)
            {
                _repository.Create(appointment);

                return RedirectToAction("Details", new {id = appointment.Id});
            }

            return View("Create", appointment);
        }

        // GET: Appointments/Edit/5
        [HttpGet]
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            var appointment = _repository.FindById(id.Value);
            if (appointment == null)
            {
                return HttpNotFound();
            }

            ViewBag.Timezones = Timezones;
            return View(appointment);
        }

        // POST: /Appointments/Edit/5
        [HttpPost]
        public ActionResult Edit([Bind(Include = "ID,Name,PhoneNumber,Time,Timezone")] Appointment appointment)
        {
            if (ModelState.IsValid)
            {
                _repository.Update(appointment);
                return RedirectToAction("Details", new { id = appointment.Id });
            }
            return View(appointment);
        }

        // DELETE: Appointments/Delete/5
        [HttpDelete]
        public ActionResult Delete(int id)
        {
            _repository.Delete(id);
            return RedirectToAction("Index");
        }
    }
}
```

That's how we return the list of appointments, now we need to render them. Let's look at the Appointments template for that.

## Displaying All Appointments

The index view lists all appointments which are sorted by Id. The only thing we need to add to fulfil our user story is a delete button. We'll add the edit button just for kicks.

### HTML Helpers

You may notice that instead of hard-coding the urls for Edit and Delete we are using an ASP.NET MVC [HTML Helpers](https://msdn.microsoft.com/en-us/library/system.web.mvc.htmlhelper.aspx). If you view the rendered markup you will see these paths.

* `/Appointments/Edit/`*`id`* for edit
* `/Appointments/Delete/`*`id`* for delete

`AppointmentsController.cs` contains methods which handle both the edit and delete operations.

#### Display all Appointments

```razor AppointmentReminders.Web/Views/Appointments/Index.cshtml
@using AppointmentReminders.Web.Extensions
@model IEnumerable<AppointmentReminders.Web.Models.Appointment>

<div class="page-header">
    <h1>Appointments</h1>
</div>

<table class="table table-striped">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Id)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.PhoneNumber)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Time)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.CreatedAt)
        </th>
        <th>
            Actions
        </th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.ActionLink(item.Id.ToString(), "Details", new {Controller = "Appointments", id = item.Id})
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.PhoneNumber)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Time)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.CreatedAt)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { Controller = "Appointments", id = item.Id }, new { @class = "btn btn-default btn-xs" })

            @Html.DeleteLink("Delete", "Appointments",
            new { id = item.Id },
            new { @class = "btn btn-danger btn-xs", onclick = "return confirm('Are you sure?');" })
        </td>
    </tr>
}

</table>

@Html.ActionLink("New", "Create", new { Controller = "Appointments" }, new { @class = "btn btn-primary" })
```

Now that we have the ability to create, view, edit, and delete appointments, we can dig into the fun part: scheduling a recurring job that will send out reminders via SMS when an appointment is coming up!

## Sending Reminders

> As an appointment system, I want to notify a user via SMS an arbitrary interval before a future appointment.

There are a lot of ways to build this part of our application, but no matter how you implement it there should be two moving parts:

* A script that checks the database for any appointment that is upcoming, and then sends a SMS.
* A worker that runs that script continuously.

Let's take a look at how we decided to implement the latter with [Hangfire](https://hangfire.io/).

## Working with Hangfire

If you've never used a job scheduler before, [you may want to check out this post by Scott Hanselman](https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx) that shows a few ways to run background tasks in ASP.NET MVC. We decided to use Hangfire because of its simplicity. If you have a better way to schedule jobs in ASP.NET MVC, [let us know](https://twitter.com/twilio).

Hangfire needs a backend of some kind to queue the upcoming jobs. In this implementation, we're using [SQL Server Database](https://docs.hangfire.io/en/latest/configuration/using-sql-server.html), but it's possible to use a different data store. You can check their [documentation](https://docs.hangfire.io/en/latest/configuration/index.html) for further details.

#### Hangfire Dependencies

```xml AppointmentReminders.Web/packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Antlr" version="3.5.0.2" targetFramework="net472" />
  <package id="EntityFramework" version="6.4.4" targetFramework="net472" />
  <package id="Hangfire" version="1.7.19" targetFramework="net472" />
  <package id="Hangfire.Core" version="1.7.18" targetFramework="net472" />
  <package id="Hangfire.SqlServer" version="1.7.18" targetFramework="net472" />
  <package id="JWT" version="7.3.1" targetFramework="net472" />
  <package id="Microsoft.AspNet.Mvc" version="5.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.Razor" version="3.2.7" targetFramework="net472" />
  <package id="Microsoft.AspNet.Web.Optimization" version="1.1.3" targetFramework="net472" />
  <package id="Microsoft.AspNet.WebPages" version="3.2.7" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.JsonWebTokens" version="6.8.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Logging" version="6.8.0" targetFramework="net472" />
  <package id="Microsoft.IdentityModel.Tokens" version="6.8.0" targetFramework="net472" />
  <package id="Microsoft.Owin" version="4.1.1" targetFramework="net472" />
  <package id="Microsoft.Owin.Host.SystemWeb" version="4.1.1" targetFramework="net472" />
  <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net472" />
  <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
  <package id="Owin" version="1.0" targetFramework="net472" />
  <package id="Portable.BouncyCastle" version="1.8.9" targetFramework="net472" />
  <package id="Portable.JWT" version="1.0.5" targetFramework="net472" />
  <package id="RestSharp" version="106.11.7" targetFramework="net472" />
  <package id="System.IdentityModel.Tokens.Jwt" version="6.8.0" targetFramework="net472" />
  <package id="Twilio" version="5.53.0" targetFramework="net472" />
  <package id="WebGrease" version="1.6.0" targetFramework="net472" />
</packages>
```

Now that we have included Hangfire dependencies into the project, let's take a look at how to configure it to use it in our appointment reminders application.

## Hangfire Configuration Class

We created a class named `Hangfire` to configure our job scheduler. This class defines two static methods:

1. `ConfigureHangfire` to set initialization parameters for the job scheduler.
2. `InitialzeJobs` to specify which recurring jobs should be run, and how often they should run.

```cs title="Hangfire Configuration" description="AppointmentReminders.Web/App_Start/Hangfire.cs"
using Hangfire;
using Owin;

namespace AppointmentReminders.Web
{
    public class Hangfire
    {
        public static void ConfigureHangfire(IAppBuilder app)
        {
            GlobalConfiguration.Configuration
                .UseSqlServerStorage("DefaultConnection");

            app.UseHangfireDashboard("/jobs");
            app.UseHangfireServer();
        }

        public static void InitializeJobs()
        {
            RecurringJob.AddOrUpdate<Workers.SendNotificationsJob>(job => job.Execute(), Cron.Minutely);
        }
    }
}
```

That's it for the configuration. Let's take a quick look next at how we start up the job scheduler.

## Starting the Job Scheduler

This ASP.NET MVC project is an [OWIN-based application](https://www.asp.net/aspnet/overview/owin-and-katana/owin-startup-class-detection), which allows us to create a startup class to run any custom initialization logic required in our application. This is the preferred location to start Hangfire - [check out their configuration docs for more information](https://docs.hangfire.io/en/latest/configuration/).

```cs title="Start Hangfire" description="AppointmentReminders.Web/Startup.c"
// !mark(4:15)
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(AppointmentReminders.Web.Startup))]
namespace AppointmentReminders.Web
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            Hangfire.ConfigureHangfire(app);
            Hangfire.InitializeJobs();
        }
    }
}
```

Now that we've started the job scheduler, let's take a look at the logic that gets executed when our job runs.

## Notification Background Job

In this class, we define a method called `Execute` which is called every minute by Hangfire. Every time the job runs, we need to:

1. Get a list of upcoming appointments that require notifications to be sent out
2. Use Twilio to send appointment reminders via SMS

The `AppointmentsFinder` class queries our SQL Server database to find all the appointments whose date and time are coming up soon. For each of those appointments, we'll use the Twilio REST API to send out a formatted message.

```cs title="Notifications Background Job" description="AppointmentReminders.Web/Workers/SendNotificationsJob.cs"
using System;
using System.Collections.Generic;
using AppointmentReminders.Web.Domain;
using AppointmentReminders.Web.Models;
using AppointmentReminders.Web.Models.Repository;
using WebGrease.Css.Extensions;

namespace AppointmentReminders.Web.Workers
{
    public class SendNotificationsJob
    {
        private const string MessageTemplate =
            "Hi {0}. Just a reminder that you have an appointment coming up at {1}.";

        public void Execute()
        {
            var twilioRestClient = new Domain.Twilio.RestClient();

            AvailableAppointments().ForEach(appointment =>
                twilioRestClient.SendSmsMessage(
                appointment.PhoneNumber,
                string.Format(MessageTemplate, appointment.Name, appointment.Time.ToString("t"))));
        }

        private static IEnumerable<Appointment> AvailableAppointments()
        {
            return new AppointmentsFinder(new AppointmentRepository(), new TimeConverter())
                .FindAvailableAppointments(DateTime.Now);
        }
    }
}
```

Now that we are retrieving a list of upcoming Appointments, let's take a look next at how we use Twilio to send SMS notifications.

## Twilio REST API Request

This class is responsible for reading our Twilio account credentials from `Web.config`, and using the Twilio REST API to actually send out a notification to our users. We also need a [Twilio number](/user/account/phone-numbers/incoming) to use as the sender for the text message. Actually sending the message is a single line of code!

```cs title="Send SMS with Twilio" description="AppointmentReminders.Web/Domain/Twilio/RestClient.cs"
// !mark(6:23)
using System.Web.Configuration;
using Twilio.Clients;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;

namespace AppointmentReminders.Web.Domain.Twilio
{
    public class RestClient
    {
        private readonly ITwilioRestClient _client;
        private readonly string _accountSid = WebConfigurationManager.AppSettings["AccountSid"];
        private readonly string _authToken = WebConfigurationManager.AppSettings["AuthToken"];
        private readonly string _twilioNumber = WebConfigurationManager.AppSettings["TwilioNumber"];

        public RestClient()
        {
            _client = new TwilioRestClient(_accountSid, _authToken);
        }

        public RestClient(ITwilioRestClient client)
        {
            _client = client;
        }

        public void SendSmsMessage(string phoneNumber, string message)
        {
            var to = new PhoneNumber(phoneNumber);
            MessageResource.Create(
                to,
                from: new PhoneNumber(_twilioNumber),
                body: message,
                client: _client);
        }
    }
}
```

Fun tutorial, right? Where can we take it from here?

## Where to Next?

And with a little code and a dash of configuration, we're ready to get automated appointment reminders firing in our application. Good work!

If you are a C# developer working with Twilio, you might want to check out other tutorials:

**[Click to Call](/docs/voice/sdks/javascript/get-started)**

Put a button on your web page that connects visitors to live support or sales people via telephone.

[**Two-Factor Authentication**](/docs/authy/quickstart/dotnet-core-csharp-two-factor-authentication)

Improve the security of your Flask app's login functionality by adding two-factor authentication via text message.
