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

Masked Phone Numbers with C# and ASP.NET MVC


This ASP.NET(link takes you to an external page) sample application is modeled after the amazing rental experience created by AirBnB(link takes you to an external page), but with more Klingons(link takes you to an external page).

Host users can offer rental properties which other guest users can reserve. The guest and the host can then anonymously communicate via a disposable Twilio phone number created just for a reservation. In this tutorial, we'll show you the key bits of code to make this work.

To run this sample app yourself, download the code and follow the instructions on GitHub(link takes you to an external page).

(warning)

Warning

Read how Lyft uses masked phone numbers to let customers securely contact drivers(link takes you to an external page)


Create a Reservation

create-a-reservation page anchor

The first step in connecting a guest and host is creating a reservation. Here, we handle a form submission for a new reservation which contains the message.

AirTNG.Web/Controllers/ReservationsController.cs


_150
using System;
_150
using System.Threading.Tasks;
_150
using System.Web.Mvc;
_150
using AirTNG.Web.Domain.PhoneNumber;
_150
using AirTNG.Web.Domain.Reservations;
_150
using AirTNG.Web.Models;
_150
using AirTNG.Web.Models.Repository;
_150
using AirTNG.Web.ViewModels;
_150
using Microsoft.AspNet.Identity;
_150
using Twilio.TwiML;
_150
using Twilio.TwiML.Mvc;
_150
_150
namespace AirTNG.Web.Controllers
_150
{
_150
[Authorize]
_150
public class ReservationsController : TwilioController
_150
{
_150
private readonly IVacationPropertiesRepository _vacationPropertiesRepository;
_150
private readonly IReservationsRepository _reservationsRepository;
_150
private readonly IUsersRepository _usersRepository;
_150
private readonly INotifier _notifier;
_150
private readonly IPurchaser _phoneNumberPurchaser;
_150
_150
public Func<string> UserId;
_150
_150
public ReservationsController() : this(
_150
new VacationPropertiesRepository(),
_150
new ReservationsRepository(),
_150
new UsersRepository(),
_150
new Notifier(),
_150
new Purchaser()) { }
_150
_150
public ReservationsController(
_150
IVacationPropertiesRepository vacationPropertiesRepository,
_150
IReservationsRepository reservationsRepository,
_150
IUsersRepository usersRepository,
_150
INotifier notifier,
_150
IPurchaser phoneNumberPurchaser)
_150
{
_150
_vacationPropertiesRepository = vacationPropertiesRepository;
_150
_reservationsRepository = reservationsRepository;
_150
_usersRepository = usersRepository;
_150
_notifier = notifier;
_150
_phoneNumberPurchaser = phoneNumberPurchaser;
_150
UserId = () => User.Identity.GetUserId();
_150
}
_150
_150
public async Task<ActionResult> Index()
_150
{
_150
var user = await _usersRepository.FindAsync(UserId());
_150
var reservations = user.Reservations;
_150
_150
return View(reservations);
_150
}
_150
_150
// GET: Reservations/Create
_150
public async Task<ActionResult> Create(int id)
_150
{
_150
var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);
_150
var reservation = new ReservationViewModel
_150
{
_150
ImageUrl = vacationProperty.ImageUrl,
_150
Description = vacationProperty.Description,
_150
VacationPropertyId = vacationProperty.Id,
_150
VacationPropertyDescription = vacationProperty.Description,
_150
UserName = vacationProperty.Owner.Name,
_150
UserPhoneNumber = vacationProperty.Owner.PhoneNumber,
_150
};
_150
_150
return View(reservation);
_150
}
_150
_150
// POST: Reservations/Create
_150
[HttpPost]
_150
public async Task<ActionResult> Create(ReservationViewModel model)
_150
{
_150
if (ModelState.IsValid)
_150
{
_150
var reservation = new Reservation
_150
{
_150
Message = model.Message,
_150
UserId = UserId(), // This is the reservee user ID
_150
VactionPropertyId = model.VacationPropertyId,
_150
Status = ReservationStatus.Pending,
_150
CreatedAt = DateTime.Now
_150
};
_150
_150
await _reservationsRepository.CreateAsync(reservation);
_150
await _reservationsRepository.LoadNavigationPropertiesAsync(reservation);
_150
_150
await _notifier.SendNotificationAsync(reservation);
_150
_150
return RedirectToAction("Index", "VacationProperties");
_150
}
_150
_150
return View(model);
_150
}
_150
_150
// POST Reservations/Handle
_150
[HttpPost]
_150
[AllowAnonymous]
_150
public async Task<ActionResult> Handle(string from, string body)
_150
{
_150
string smsResponse;
_150
_150
try
_150
{
_150
var host = await _usersRepository.FindByPhoneNumberAsync(from);
_150
var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);
_150
_150
var smsRequest = body;
_150
if (IsSmsRequestAccepted(smsRequest))
_150
{
_150
var purchasedPhoneNumber = _phoneNumberPurchaser.Purchase(host.AreaCode);
_150
_150
reservation.Status = ReservationStatus.Confirmed;
_150
reservation.AnonymousPhoneNumber = purchasedPhoneNumber.PhoneNumber;
_150
}
_150
else
_150
{
_150
reservation.Status = ReservationStatus.Rejected;
_150
}
_150
_150
await _reservationsRepository.UpdateAsync(reservation);
_150
smsResponse =
_150
string.Format("You have successfully {0} the reservation", reservation.Status);
_150
}
_150
catch (Exception)
_150
{
_150
smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
_150
}
_150
_150
return TwiML(Respond(smsResponse));
_150
}
_150
_150
private static TwilioResponse Respond(string message)
_150
{
_150
var response = new TwilioResponse();
_150
response.Message(message);
_150
_150
return response;
_150
}
_150
_150
private static bool IsSmsRequestAccepted(string smsRequest)
_150
{
_150
return smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
_150
smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase);
_150
}
_150
}
_150
}

Part of our reservation system is receiving reservation requests from potential renters. However, these reservations need to be confirmed. Let's see how we would handle this step.


Before finishing with the reservation, the host needs to confirm that the property was reserved. Learn how to automate this process in our first AirTNG tutorial, Workflow Automation(link takes you to an external page).

AirTNG.Web/Controllers/ReservationsController.cs


_150
using System;
_150
using System.Threading.Tasks;
_150
using System.Web.Mvc;
_150
using AirTNG.Web.Domain.PhoneNumber;
_150
using AirTNG.Web.Domain.Reservations;
_150
using AirTNG.Web.Models;
_150
using AirTNG.Web.Models.Repository;
_150
using AirTNG.Web.ViewModels;
_150
using Microsoft.AspNet.Identity;
_150
using Twilio.TwiML;
_150
using Twilio.TwiML.Mvc;
_150
_150
namespace AirTNG.Web.Controllers
_150
{
_150
[Authorize]
_150
public class ReservationsController : TwilioController
_150
{
_150
private readonly IVacationPropertiesRepository _vacationPropertiesRepository;
_150
private readonly IReservationsRepository _reservationsRepository;
_150
private readonly IUsersRepository _usersRepository;
_150
private readonly INotifier _notifier;
_150
private readonly IPurchaser _phoneNumberPurchaser;
_150
_150
public Func<string> UserId;
_150
_150
public ReservationsController() : this(
_150
new VacationPropertiesRepository(),
_150
new ReservationsRepository(),
_150
new UsersRepository(),
_150
new Notifier(),
_150
new Purchaser()) { }
_150
_150
public ReservationsController(
_150
IVacationPropertiesRepository vacationPropertiesRepository,
_150
IReservationsRepository reservationsRepository,
_150
IUsersRepository usersRepository,
_150
INotifier notifier,
_150
IPurchaser phoneNumberPurchaser)
_150
{
_150
_vacationPropertiesRepository = vacationPropertiesRepository;
_150
_reservationsRepository = reservationsRepository;
_150
_usersRepository = usersRepository;
_150
_notifier = notifier;
_150
_phoneNumberPurchaser = phoneNumberPurchaser;
_150
UserId = () => User.Identity.GetUserId();
_150
}
_150
_150
public async Task<ActionResult> Index()
_150
{
_150
var user = await _usersRepository.FindAsync(UserId());
_150
var reservations = user.Reservations;
_150
_150
return View(reservations);
_150
}
_150
_150
// GET: Reservations/Create
_150
public async Task<ActionResult> Create(int id)
_150
{
_150
var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);
_150
var reservation = new ReservationViewModel
_150
{
_150
ImageUrl = vacationProperty.ImageUrl,
_150
Description = vacationProperty.Description,
_150
VacationPropertyId = vacationProperty.Id,
_150
VacationPropertyDescription = vacationProperty.Description,
_150
UserName = vacationProperty.Owner.Name,
_150
UserPhoneNumber = vacationProperty.Owner.PhoneNumber,
_150
};
_150
_150
return View(reservation);
_150
}
_150
_150
// POST: Reservations/Create
_150
[HttpPost]
_150
public async Task<ActionResult> Create(ReservationViewModel model)
_150
{
_150
if (ModelState.IsValid)
_150
{
_150
var reservation = new Reservation
_150
{
_150
Message = model.Message,
_150
UserId = UserId(), // This is the reservee user ID
_150
VactionPropertyId = model.VacationPropertyId,
_150
Status = ReservationStatus.Pending,
_150
CreatedAt = DateTime.Now
_150
};
_150
_150
await _reservationsRepository.CreateAsync(reservation);
_150
await _reservationsRepository.LoadNavigationPropertiesAsync(reservation);
_150
_150
await _notifier.SendNotificationAsync(reservation);
_150
_150
return RedirectToAction("Index", "VacationProperties");
_150
}
_150
_150
return View(model);
_150
}
_150
_150
// POST Reservations/Handle
_150
[HttpPost]
_150
[AllowAnonymous]
_150
public async Task<ActionResult> Handle(string from, string body)
_150
{
_150
string smsResponse;
_150
_150
try
_150
{
_150
var host = await _usersRepository.FindByPhoneNumberAsync(from);
_150
var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);
_150
_150
var smsRequest = body;
_150
if (IsSmsRequestAccepted(smsRequest))
_150
{
_150
var purchasedPhoneNumber = _phoneNumberPurchaser.Purchase(host.AreaCode);
_150
_150
reservation.Status = ReservationStatus.Confirmed;
_150
reservation.AnonymousPhoneNumber = purchasedPhoneNumber.PhoneNumber;
_150
}
_150
else
_150
{
_150
reservation.Status = ReservationStatus.Rejected;
_150
}
_150
_150
await _reservationsRepository.UpdateAsync(reservation);
_150
smsResponse =
_150
string.Format("You have successfully {0} the reservation", reservation.Status);
_150
}
_150
catch (Exception)
_150
{
_150
smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
_150
}
_150
_150
return TwiML(Respond(smsResponse));
_150
}
_150
_150
private static TwilioResponse Respond(string message)
_150
{
_150
var response = new TwilioResponse();
_150
response.Message(message);
_150
_150
return response;
_150
}
_150
_150
private static bool IsSmsRequestAccepted(string smsRequest)
_150
{
_150
return smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
_150
smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase);
_150
}
_150
}
_150
}

Once the reservation is confirmed, we need to purchase a Twilio number that the guest and host can use to communicate.


Purchase a Twilio Number

purchase-a-twilio-number page anchor

Here we use a Twilio C# Helper Library(link takes you to an external page) to search for and buy a new phone number to associate with the reservation. When we purchase the number, we designate a Twilio Application that will handle webhook(link takes you to an external page) requests when the new number receives an incoming call or text.

We then save the new phone number on our Reservation model, so when our app receives calls or texts to this number, we'll know which reservation the call or text belongs to.

AirTNG.Web/Domain/PhoneNumber/Purchaser.cs


_55
using System.Linq;
_55
using AirTNG.Web.Domain.Twilio;
_55
using Twilio;
_55
_55
namespace AirTNG.Web.Domain.PhoneNumber
_55
{
_55
public interface IPurchaser
_55
{
_55
IncomingPhoneNumber Purchase(string areaCode);
_55
}
_55
_55
public class Purchaser : IPurchaser
_55
{
_55
private readonly TwilioRestClient _client;
_55
_55
public Purchaser() : this(new TwilioRestClient(Credentials.AccountSID, Credentials.AuthToken)) { }
_55
_55
public Purchaser(TwilioRestClient client)
_55
{
_55
_client = client;
_55
}
_55
_55
/// <summary>
_55
/// Purchase the first available phone number.
_55
/// </summary>
_55
/// <param name="areaCode">The area code</param>
_55
/// <returns>The purchased phone number</returns>
_55
public IncomingPhoneNumber Purchase(string areaCode)
_55
{
_55
var phoneNumberOptions = new PhoneNumberOptions
_55
{
_55
PhoneNumber = SearchForFirstAvailablePhoneNumber(areaCode),
_55
VoiceApplicationSid = Credentials.ApplicationSID
_55
};
_55
_55
return _client.AddIncomingPhoneNumber(phoneNumberOptions);
_55
}
_55
_55
private string SearchForFirstAvailablePhoneNumber(string areaCode)
_55
{
_55
var searchParams = new AvailablePhoneNumberListRequest
_55
{
_55
AreaCode = areaCode,
_55
VoiceEnabled = true,
_55
SmsEnabled = true
_55
};
_55
_55
return _client
_55
.ListAvailableLocalPhoneNumbers("US", searchParams)
_55
.AvailablePhoneNumbers
_55
.First() // We're only interested in the first available phone number.
_55
.PhoneNumber;
_55
}
_55
}
_55
}

Now that each reservation has a Twilio Phone Number, we can see how the application will look up reservations as guest or host calls come in.


When someone sends an SMS or calls one of the Twilio numbers you have configured, Twilio makes a request to the URL you set in the TwiML app. In this request, Twilio includes some useful information including:

  • The From number that initially called or sent an SMS.
  • The To Twilio number that triggered this request.

Take a look at Twilio's SMS Documentation and Twilio's Voice Documentation for a full list of the parameters you can use.

In our controller, we use the to parameter sent by Twilio to find a reservation that has the number we bought stored in it, as this is the number both hosts and guests will call and send SMS to.

AirTNG.Web/Controllers/PhoneExchangeController.cs


_66
using System.Threading.Tasks;
_66
using System.Web.Mvc;
_66
using AirTNG.Web.Models.Repository;
_66
using Twilio.TwiML;
_66
using Twilio.TwiML.Mvc;
_66
_66
namespace AirTNG.Web.Controllers
_66
{
_66
public class PhoneExchangeController : TwilioController
_66
{
_66
private readonly IReservationsRepository _repository;
_66
_66
public PhoneExchangeController() : this(new ReservationsRepository()) { }
_66
_66
public PhoneExchangeController(IReservationsRepository repository)
_66
{
_66
_repository = repository;
_66
}
_66
_66
// POST: PhoneExchange/InterconnectUsingSms
_66
[HttpPost]
_66
public async Task<ActionResult> InterconnectUsingSms(string from, string to, string body)
_66
{
_66
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
_66
_66
var response = new TwilioResponse();
_66
response.Message(body, new {to = outgoingPhoneNumber});
_66
_66
return TwiML(response);
_66
}
_66
_66
// POST: PhoneExchange/InterconnectUsingVoice
_66
[HttpPost]
_66
public async Task<ActionResult> InterconnectUsingVoice(string from, string to)
_66
{
_66
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
_66
_66
var response = new TwilioResponse();
_66
response.Play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3");
_66
response.Dial(outgoingPhoneNumber);
_66
_66
return TwiML(response);
_66
}
_66
_66
private async Task<string> GatherOutgoingPhoneNumberAsync(
_66
string incomingPhoneNumber, string anonymousPhoneNumber)
_66
{
_66
var outgoingPhoneNumber = string.Empty;
_66
var reservation = await _repository.FindByAnonymousPhoneNumberAsync(anonymousPhoneNumber);
_66
_66
// Connect from Guest to Host
_66
if (reservation.Guest.PhoneNumber.Equals(incomingPhoneNumber))
_66
{
_66
outgoingPhoneNumber = reservation.Host.PhoneNumber;
_66
}
_66
_66
// Connect from Host to Guest
_66
if (reservation.Host.PhoneNumber.Equals(incomingPhoneNumber))
_66
{
_66
outgoingPhoneNumber = reservation.Guest.PhoneNumber;
_66
}
_66
_66
return outgoingPhoneNumber;
_66
}
_66
}
_66
}

Next, let's see how to connect the guest and the host via SMS.


Our Twilio application should be configured to send HTTP requests to this controller method on any incoming text message. Our app responds with TwiML to tell Twilio what to do in response to the message.

If the initial message sent to the anonymous number was sent by the host, we forward it on to the guest. Conversely, if the original message was sent by the guest, we forward it to the host.

To find the outgoing number we'll use the GatherOutgoingPhoneNumberAsync helper method.

AirTNG.Web/Controllers/PhoneExchangeController.cs


_66
using System.Threading.Tasks;
_66
using System.Web.Mvc;
_66
using AirTNG.Web.Models.Repository;
_66
using Twilio.TwiML;
_66
using Twilio.TwiML.Mvc;
_66
_66
namespace AirTNG.Web.Controllers
_66
{
_66
public class PhoneExchangeController : TwilioController
_66
{
_66
private readonly IReservationsRepository _repository;
_66
_66
public PhoneExchangeController() : this(new ReservationsRepository()) { }
_66
_66
public PhoneExchangeController(IReservationsRepository repository)
_66
{
_66
_repository = repository;
_66
}
_66
_66
// POST: PhoneExchange/InterconnectUsingSms
_66
[HttpPost]
_66
public async Task<ActionResult> InterconnectUsingSms(string from, string to, string body)
_66
{
_66
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
_66
_66
var response = new TwilioResponse();
_66
response.Message(body, new {to = outgoingPhoneNumber});
_66
_66
return TwiML(response);
_66
}
_66
_66
// POST: PhoneExchange/InterconnectUsingVoice
_66
[HttpPost]
_66
public async Task<ActionResult> InterconnectUsingVoice(string from, string to)
_66
{
_66
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
_66
_66
var response = new TwilioResponse();
_66
response.Play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3");
_66
response.Dial(outgoingPhoneNumber);
_66
_66
return TwiML(response);
_66
}
_66
_66
private async Task<string> GatherOutgoingPhoneNumberAsync(
_66
string incomingPhoneNumber, string anonymousPhoneNumber)
_66
{
_66
var outgoingPhoneNumber = string.Empty;
_66
var reservation = await _repository.FindByAnonymousPhoneNumberAsync(anonymousPhoneNumber);
_66
_66
// Connect from Guest to Host
_66
if (reservation.Guest.PhoneNumber.Equals(incomingPhoneNumber))
_66
{
_66
outgoingPhoneNumber = reservation.Host.PhoneNumber;
_66
}
_66
_66
// Connect from Host to Guest
_66
if (reservation.Host.PhoneNumber.Equals(incomingPhoneNumber))
_66
{
_66
outgoingPhoneNumber = reservation.Guest.PhoneNumber;
_66
}
_66
_66
return outgoingPhoneNumber;
_66
}
_66
}
_66
}

Let's see how to connect the guest and the host via phone call next.


Our Twilio application will send HTTP requests to this method on any incoming voice call. Our app responds with TwiML instructions that tell Twilio to Play an introductory MP3 audio file and then Dial either the guest or host, depending on who initiated the call.

AirTNG.Web/Controllers/PhoneExchangeController.cs


_66
using System.Threading.Tasks;
_66
using System.Web.Mvc;
_66
using AirTNG.Web.Models.Repository;
_66
using Twilio.TwiML;
_66
using Twilio.TwiML.Mvc;
_66
_66
namespace AirTNG.Web.Controllers
_66
{
_66
public class PhoneExchangeController : TwilioController
_66
{
_66
private readonly IReservationsRepository _repository;
_66
_66
public PhoneExchangeController() : this(new ReservationsRepository()) { }
_66
_66
public PhoneExchangeController(IReservationsRepository repository)
_66
{
_66
_repository = repository;
_66
}
_66
_66
// POST: PhoneExchange/InterconnectUsingSms
_66
[HttpPost]
_66
public async Task<ActionResult> InterconnectUsingSms(string from, string to, string body)
_66
{
_66
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
_66
_66
var response = new TwilioResponse();
_66
response.Message(body, new {to = outgoingPhoneNumber});
_66
_66
return TwiML(response);
_66
}
_66
_66
// POST: PhoneExchange/InterconnectUsingVoice
_66
[HttpPost]
_66
public async Task<ActionResult> InterconnectUsingVoice(string from, string to)
_66
{
_66
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
_66
_66
var response = new TwilioResponse();
_66
response.Play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3");
_66
response.Dial(outgoingPhoneNumber);
_66
_66
return TwiML(response);
_66
}
_66
_66
private async Task<string> GatherOutgoingPhoneNumberAsync(
_66
string incomingPhoneNumber, string anonymousPhoneNumber)
_66
{
_66
var outgoingPhoneNumber = string.Empty;
_66
var reservation = await _repository.FindByAnonymousPhoneNumberAsync(anonymousPhoneNumber);
_66
_66
// Connect from Guest to Host
_66
if (reservation.Guest.PhoneNumber.Equals(incomingPhoneNumber))
_66
{
_66
outgoingPhoneNumber = reservation.Host.PhoneNumber;
_66
}
_66
_66
// Connect from Host to Guest
_66
if (reservation.Host.PhoneNumber.Equals(incomingPhoneNumber))
_66
{
_66
outgoingPhoneNumber = reservation.Guest.PhoneNumber;
_66
}
_66
_66
return outgoingPhoneNumber;
_66
}
_66
}
_66
}

That's it! We've just implemented anonymous communications that allow your customers to connect while protecting their privacy.


If you're a PHP developer working with Twilio, you might want to check out these other tutorials:

IVR: Screening and Recording

Save time and remove distractions by adding call screening and recording to your 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! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio(link takes you to an external page) to let us know what you think.


Rate this page: