Engineering

A day in the life of a healthcare developer attempting to use Redox

Posted December 17, 2018
By Chris Hennen

Today, I’m building a simple patient demographics web application using C# .NET, MVC5, and Web API 2.0.

All of the code you see here is included in the Redox .NET Sample App on GitHub. The end goal of this project is to see how hard it is to get started with sending and receiving healthcare data with Redox when I already have a functional application built in a sort-of recent framework.

I’m doing this on a computer that I built about 10 years ago running copy of Windows 10 using a cheap knock-off mechanical keyboard and a Logitech G5 laser gaming mouse with optional precision weighting system calibrated to whatever the max number of included weights add up to. You can add them if you want, the image is right here. Also shown are the extra 5GB of RAM that I can’t use because 4 out of 6 of my motherboard’s DIMM slots decided to stop working about four years ago.

Building The Application

To get a bare-bones application with an attached local database set up, I just followed this article from Microsoft. It goes through how to make an ASP.NET MVC project, how to install Entity Framework (Microsoft’s ORM of choice), and how to set up your first table. I named my project SampleSite because I am not creative. I made a table for patient demographic info where we can store every possible detail about a person who uses the healthcare system. My Patient.edmx file looks like this, and provides a class called Patient that I can use in code when interacting with database entries:

Great! Now that I have a 100% perfect data model that will never go out of date or need to be modified (with phone numbers and SSN represented as Decimal type, SMH), I’ll make a CRUD-y PatientService with the expected functions:

using SampleSite.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using PatientAdmin = RedoxApi.Models.Patientadmin;

namespace SampleSite.Services
{
    public static class PatientService
    {
        public static IEnumerable<Patient> GetAllPatients()
        {
            var entities = new PatientsDBEntities();
            return entities.Patients.ToList();
        }

        public static Patient GetPatient(int patientId)
        {
            var entities = new PatientsDBEntities();
            return entities.Patients.SingleOrDefault(x => x.Id == patientId);
        }

        public static void SavePatient(Patient patient)
        {
            var entities = new PatientsDBEntities();
            if (!entities.Patients.Any(x => x.First == patient.First && 
                                            x.Last == patient.Last && 
                                            x.BirthDate == patient.BirthDate && 
                                            x.SocialSecurityNumber == patient.SocialSecurityNumber))
            {
                entities.Patients.Add(patient);
                entities.SaveChanges();
            }
        }

        public static void UpdatePatient(Patient patient)
        {
            var entities = new PatientsDBEntities();
            var dbPatient = entities.Patients.Single(x => x.Id == patient.Id);
            dbPatient.First = patient.First;
            dbPatient.Last = patient.Last;
            dbPatient.MRN = patient.MRN;
            dbPatient.Gender = patient.Gender;
            dbPatient.Email = patient.Email;
            dbPatient.HomePhone = patient.HomePhone;
            dbPatient.BirthDate = patient.BirthDate;
            dbPatient.CellPhone = patient.CellPhone;
            dbPatient.AddressLine1 = patient.AddressLine1;
            dbPatient.AddressLine2 = patient.AddressLine2;
            dbPatient.City = patient.City;
            dbPatient.State = patient.State;
            dbPatient.ZipCode = patient.ZipCode;
            dbPatient.SocialSecurityNumber = patient.SocialSecurityNumber;

            entities.SaveChanges();
        }

        public static void DeletePatient(int patientId)
        {
            var entities = new PatientsDBEntities();
            var patient = entities.Patients.Single(x => x.Id == patientId);
            entities.Patients.Remove(patient);
            entities.SaveChanges();
        }
    }
}

Next, we’ll write a controller to perform these operations. I’m using the default routing mechanism for MVC, which basically reflects on the files in the controllers folder to figure out what routes should be defined. In general, the routes look like this string from SampleSite/app_start/routeConfig.cs:

routes.MapRoute(
  name: "Default",
  url: "{controller}/{action}/{id}",
  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

I made a controller called PatientController.cs (in MVC and WebApi, if the filename doesn’t end with Controller.cs, then the framework doesn’t think it’s a controller because of magical reasons that I can’t hope to ever understand). It has routes for doing CRUD-y things, and I put the intended route above each function because magical routing rules are hard to remember. A wise .NET developer would probably use attribute routing here.

using SampleSite.Models;
using SampleSite.Services;
using System;
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SampleSite.Controllers
{
    public class PatientController : Controller
    {
        // GET: Patient
        public ActionResult Index()
        {
            var patients = PatientService.GetAllPatients();
            return View(patients);
        }

        // GET: Patient/Details/5
        public ActionResult Details(int id)
        {
            var patient = PatientService.GetPatient(id);
            return View(patient);
        }

        // GET: Patient/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Patient/Create
        [HttpPost]
        public ActionResult Create(Patient patient)
        {
            if (ModelState.IsValid)
            {
                //save a patient here
                try
                {
                    PatientService.SavePatient(patient);
                    return RedirectToAction("Index");
                }
                catch (DbEntityValidationException e)
                {
                    foreach (var err in e.EntityValidationErrors)
                    {
                        foreach (var msg in err.ValidationErrors)
                        {
                            ModelState.AddModelError(msg.PropertyName, msg.ErrorMessage);
                        }
                    }
                    return View();
                }
            }
            else
            {
                return View();
            }
        }

        // GET: Patient/Edit/5
        public ActionResult Edit(int id)
        {
            var patient = PatientService.GetPatient(id);
            return View(patient);
        }

        // POST: Patient/Edit/5
        [HttpPost]
        public ActionResult Edit(int id, Patient patient)
        {
            try
            {
                PatientService.UpdatePatient(patient);

                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        // GET: Patient/Delete/5
        public ActionResult Delete(int id)
        {
            var patient = PatientService.GetPatient(id);
            return View(patient);
        }

        // POST: Patient/Delete/5
        [HttpPost]
        public ActionResult Delete(int id, Patient patient)
        {
            try
            {
                PatientService.DeletePatient(id);

                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }
    }
}

It’s a good practice to keep your controllers as “skinny” as possible by adhering to the MVC pattern – controllers should only manage to receive a route, gathering the necessary models, and returning the appropriate view. These controller files can quickly grow to encompass MANY routes and maintaining business logic or specific view-related info in them is an easy way to end up with an extremely long, hard-to-navigate file and lots of devs making potentially-conflicting changes to the same file.

I won’t bore anyone here with the individual view code, it is written in Razor syntax and is boring and minimal. It uses jquery and bootstrap libraries to make the front end pretty and is not fit for even showing to developers in 2016. Moving on.

The site is complete! I can view a list of patients, delete a patient, update a patient, or create a new patient. The site is very futuristic, has a clean design that rivals most current-era sites, and works entirely on form submission actions to generate all of its pages making it very responsive and performant. Behold the various views; truly these are among the greatest that modern website design could produce.

Application as a Redox Source

The next step is to get the data produced by our bleeding-edge-of-high-tech application into Redox. I happen to have written a .NET client library for this part a few months ago, so getting started was pretty easy. The client library is included in the code for this project on GitHub.

Working with the client library is straightforward – we must initialize a Redox Client object using our API key and secret from our Redox source record (don’t look at my secret pls ty):

The Redox Client object will handle authenticating requests to Redox by automatically requesting a token, remembering it for subsequent requests, and using the refresh token to generate a new token if/when necessary. The Redox Client also has classes that represent the various Redox Data Models, and functions that send data model objects directly to https://api.redoxengine.com/endpoint. So, all we have to do is supply a properly “hydrated” Redox Data Model object to the appropriate function.

I’ll instantiate a static instance of the Redox API Client in my PatientService.cs file, since that’s the only place we’ll be generating data changes that should be sent to Redox for now:

private static RedoxClient redox = new RedoxApi.RedoxClient("0fa96c7a-5f80-4249-bfce-7644d483ef44", "LSjjwJDhlBVZ7W5zdJ9lBhMwz1zkhrWpMsUOkwyj9XBl5DalQ0FRRWJ19QoTVw5SGQlPRZUo");

Now, to send the datamodel/event type as a message to our Redox source, we’ll first instantiate the relevant model from the Redox .NET SDK, map our application’s patient onto it by simply manually setting the various properties. The Redox Data Model classes’ namespaces follow the pattern Datamodel.EventType and are named after the event type.

As an example, our SavePatient function could send a PatientAdmin^NewPatient message after saving the patient in our database with the following code changes:

public static void SavePatient(Patient patient)
        {
            var entities = new PatientsDBEntities();
            if (!entities.Patients.Any(x => x.First == patient.First && 
                                            x.Last == patient.Last && 
                                            x.BirthDate == patient.BirthDate && 
                                            x.SocialSecurityNumber == patient.SocialSecurityNumber))
            {
                entities.Patients.Add(patient);
                entities.SaveChanges();

                //send the new patient to Redox
                var newPatient = new PatientAdmin.Newpatient.Newpatient();
                newPatient.Patient.Identifiers.Add( new PatientAdmin.Newpatient.Anonymous2() { ID = patient.MRN, IDType = "MRN" } );
                newPatient.Patient.Demographics.FirstName = patient.First;
                newPatient.Patient.Demographics.LastName = patient.Last;
                newPatient.Patient.Demographics.DOB = patient.BirthDate.ToString();
                newPatient.Patient.Demographics.Sex = patient.Gender;
                newPatient.Patient.Demographics.EmailAddresses.Add(patient.Email);
                newPatient.Patient.Demographics.Address.StreetAddress = patient.AddressLine1 + patient.AddressLine2;
                newPatient.Patient.Demographics.Address.City = patient.City;
                newPatient.Patient.Demographics.Address.State = patient.State;
                newPatient.Patient.Demographics.Address.ZIP = patient.ZipCode;
                newPatient.Patient.Demographics.SSN = patient.SocialSecurityNumber.ToString();
                newPatient.Patient.Demographics.PhoneNumber.Home = patient.HomePhone.ToString();
                newPatient.Patient.Demographics.PhoneNumber.Mobile = patient.CellPhone.ToString();

                var response = redox.PatientAdmin.NewPatient(newPatient);
            }
        }

Going on a quick tangent here – the Data Model classes included in the Redox.NET SDK are generated by feeding the datamodels’ JSON Schema files generated from the documentation site into the NJsonSchema package. You can see that this results in some strange naming in some cases since the JSON Schema for the models doesn’t provide names for the objects in some arrays. For those interested in how the model classes are generated, check out the model generator project on GitHub.

My first blocker came up while testing connectivity to my Redox source: Redox does not support old versions of SSL since serious hackers already cracked lower SSL versions! I’m using .NET Framework version 4.5.2 which defaults all connections to SSLv3 without negotiating to TLS (any version) and was getting handshake errors on every request to https://api.redoxengine.com. I had no idea what the error meant for a couple hours and tried some really wacky potential fixes before remembering from a Slack conversation that I’ll need to use TLS and then figuring out how to force connections to use TLS 1.2. In the past, you had to actually set a Windows registry key for .NET to use TLS, but now you can just add a line like ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; to your global.asax.cs file. Duh.

Hey, that was pretty easy. The hard part, as an app developer, is mapping my database’s Patient object into the Redox PatientAdmin data model. I might want to write a function that does this translation if I’m going to be doing it a lot, or use a tool like AutoMapper if your name is Captain Fancy Pants. Mine is not so dealwithit.gif.

Application as a Redox Destination

Let’s make sure Redox can also send data to our application by exposing an endpoint in the same project. I found a quick tutorial on how to get an existing MVC project to work with .NET Web API 2 on this really neat new site called “StackOverflow”

One funny thing about routing in Web API 2.0 is that by default, controller methods (called actions) are invoked based entirely on the URL. This brings me to a second blocker: Redox only ever POSTs to one URL, and my app needs to be able to figure out what to do based on the body of the POST request. This means the “Redox API” communication method doesn’t expect our app to be RESTy and only provides one rigid form of authentication.

To solve this in my app I could put a gigantic switch statement in one controller action or I could override the default IHttpActionSelector with something that looks at the shape of the body and calls a specific action based on whether we think the request is a data model or a verification request. The former makes for one huge controller that I suspect will need to be changed often to support new functionality and the latter is a somewhat expensive operation that must be performed for every incoming request. Neither of these approaches are ideal but I suspect there’s less future maintenance with the latter so that’s what I’m gonna do alright.

I made a new Action Selector which passes through in case our API has any non-Redox endpoints (lol why would it amirite) and had it pick the “verify” action if there is a challenge in the body, otherwise pick the action whose name was <datamodel>_<eventtype> based on the body’s Meta object. One could also create custom attributes and key on those, or hard-code the names of the controller actions, or do something else maybe. Anyway, I threw this garbage into a file called App_Start/RedoxActionSelector.cs:

using Newtonsoft.Json.Linq;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Web.Http.Controllers;

namespace SampleSite
{
    public class RedoxActionSelector : ApiControllerActionSelector
    {
        public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
        {
            if (!controllerContext.ControllerDescriptor.ControllerName.Contains("Redox"))
            {
                return base.SelectAction(controllerContext);
            }

            var requestContent = new HttpMessageContent(controllerContext.Request);
            var json = requestContent.HttpRequestMessage.Content.ReadAsStringAsync().Result;
            var body = JObject.Parse(json);

            // If we're using the redox controller due to a route like /api/redox, let's check out what's in the body
            // 1\. This could be a dang ol' verification request
            var actions = controllerContext.ControllerDescriptor.ControllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public);
            var challenge = (string)body.SelectToken("challenge");
            if (!string.IsNullOrEmpty(challenge))
            {
                var action = actions.Single(x => x.Name == "Verify");
                return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, action);
            }

            // 2\. This better be a datamodel then
            var datamodel = (string)body.SelectToken("Meta.DataModel");
            var eventType = (string)body.SelectToken("Meta.EventType");
            if (!string.IsNullOrEmpty(datamodel) && !string.IsNullOrEmpty(eventType))
            {
                // Expect there to be an action with the right name
                var action = actions.SingleOrDefault(x => x.Name == datamodel + "_" + eventType);
                if (action != null)
                {
                    return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, action);
                }
            }

            // Pft Idunno just let MVC figure it out maybe
            return base.SelectAction(controllerContext);

        }
    }
}

And told my app to replace the default action selector in App_Start/WebApiConfig.cs:

config.Services.Replace(typeof(IHttpActionSelector), new RedoxActionSelector());

Ok, so our Web API routing knows how to pick the right action based on the datamodel and event type. But before we can even receive actual messages from Redox, we need to “verify” our “destination”. We should provide an endpoint that Redox will POST to with a token we provide and a “challenge” value, and all it does is verify the token is correct and parrot the challenge in the response. The Redox CLI comes with a model for the verification request body (for POST verification, since GET will be retired at EOY).

    public class VerifyDestinationBody
{
        [JsonProperty(PropertyName = "verification-token")]
        public string verification_token { get; set; }
        public string challenge { get; set; }
    }

I’ll mention quickly that the verification request body is tough to guess based on the documentation page for destination setup; I was only able to figure it out after establishing connectivity and using a debugger to examine the body of the request. Also, the fact that the verification token is in the “verification-token” property and dashes aren’t allowed in class member names, you need to tell your favorite JSON deserializer what property (with a dash) corresponds to which class member.

I made a RedoxController to house the actions that Redox will eventually send to. You can see I’ve got one action called Verify that takes the VerificationDestinationBody as a parameter and another action for PatientAdmin^NewPatient messages. Also, I wrote a quick helper function to determine whether the request actually came from Redox by inspecting the verification-token header and making sure it matches our provided token.

using SampleSite.Services;
using System;
using System.Collections.Generic;
using System.Data.Entity.Validation;
using System.Linq;
using System.Web.Http;

namespace SampleSite.Controllers
{
    public class RedoxController : ApiController
    {
        public const string AUTH_SECRET = "jamesisadork"; // trololololol
        private bool IsAuthorized()
        {
            IEnumerable<string> headerValues;
            return (Request.Headers.TryGetValues("verification-token", out headerValues) &&
                headerValues.FirstOrDefault() == AUTH_SECRET);
        }

        [Route("api/redox")]
        [HttpPost]
        public IHttpActionResult Verify([FromBody] RedoxApi.Models.VerifyDestinationBody verification)
        {
            if (verification.verification_token == AUTH_SECRET)
            {
                // reply with the challenge value to verify with Redox
                return Ok(verification.challenge);
            }
            return BadRequest();
        }

        // Only support new patient for now. Other datamodels/event types should 404.
        [Route("api/redox")]
        [HttpPost]
        public IHttpActionResult PatientAdmin_NewPatient([FromBody] RedoxApi.Models.Patientadmin.Newpatient.Newpatient patient)
        {
            // Make sure this request comes from Redox
            if (!IsAuthorized())
            {
                return BadRequest();
            }

            // Save the new patient
            try
            {
                PatientService.SaveRedoxPatient(patient);
            }
            catch (DbEntityValidationException e)
            {
                foreach (var eve in e.EntityValidationErrors)
                {
                    Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                        eve.Entry.Entity.GetType().Name, eve.Entry.State);
                    foreach (var ve in eve.ValidationErrors)
                    {
                        Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                            ve.PropertyName, ve.ErrorMessage);
                    }
                }
                return BadRequest("Validation failed");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return BadRequest("Missing required fields probably");
            }
            return Ok();
        }
    }
}

The next hiccup is that Redox cannot, obviously, send a web request to your local machine. I’ll use ngrok to set up a secure tunnel from my machine to some randomly generated URL on the public web. My .NET application gets angry when it doesn’t receive the appropriate host header, so the command I use to set up the tunnel is ngrok http [port] –host-header=”localhost:[port]”) replacing [port] with the port that the app runs on locally.

I will set up my destination with the ngrok URL, then click the “verify and save” button and see that the destination is all set up!

If I use dev tools to send a PatientAdmin^NewPatient message to my destination, I’ll see that patient on my website in the list next to all the other patients. You can look at the actual transmission I created for this patient here. I don’t have an image to prove that it actually worked and that the patient is in my app’s patient list view, so you’ll just have to trust me or find a way to deal with the omission in your own way.

And so, in conclusion, it seems that augmenting an already perfectly crafted web application to send and receive data using .NET wasn’t terribly tricky. There were a few bumps in the road that I was able to overcome more quickly due to being a developer at Redox, and it did take more than a few hours to get everything working. If you have already connected to the Redox API, how was your experience?  Try it in your framework of choice and share the experience!

Chris’s Top 10 Takeaways:

  1. Datamodel classes are generated automatically here based on JSON schema files, but some fields are named awkwardly. I haven’t been able to figure out a way to fix this.
  2. TLS on incoming requests isn’t obvious at first, some languages/frameworks don’t report this error clearly.
  3. The body of the verification POST request is hard to determine prior to setting up an endpoint that can receive the request. This could require a deploy to a testing or production site which are usually harder to debug than if you were running locally. Good news is you can use a tool like ngrok to tunnel your localhost to the internet, but I’m not sure how well-known this tool/process is.
  4. The name of the verification token in the POST body has a dash in it. FML. (Redox will fix this)
  5. I can’t set up a different route per datamodel/event type, and so I have to parse the body of the request in order to figure out what to do with it. This doesn’t work well with .NET Web API because routing is based on URL only.
  6. The other five go here.

 

***

Redox developers are encouraged to explore new languages, frameworks, and generally expand their knowledge. As an API company eating your own dog food can be a productive exercise. Stay tuned for new features and easier ways to connect to Redox. Until then, check out more great engineering content and browse our current career opportunities.