In my previous post FHIR - Feel The 'Fire' - 1, I explained how we can connect test servers and explore healthcare resources exposed by these servers. I found this very helpful in getting started with FHIR.
In this post I will explain you how we can create our own FHIR RESTful service and expose the healthcare resources to be consumed by healthcare systems. This is based on C# Reference Implementation availalbe with FHIR specification v0.07
For this I will use WCF (Windows Communication Foundation) with C#.
Note:
-
This is not to teach you WCF REST
-
I will provide a sample FHIR RESTful implementation here. Actual implementation might be different.
Outline
-
We will create a list with two patient resources to serve as data source for our service.
-
We will implement two methods to simulate “read” operation
-
getResourceFeed (baseurl/patient)
-
This will return an atom feed with collection of patient resources
-
This will return an atom feed with collection of patient resources
-
getResource (baseurl/patient/@id)
-
This will return a patient resource based on id provided in url
-
This will return a patient resource based on id provided in url
-
getResourceFeed (baseurl/patient)
-
Provide a mechanism to return xml/json response based on HTTP header "Accept". You can also try this by passing query string parameter "format"
-
We will use following response status codes
-
HTTP 501 - Not Implemented
-
HTTP 404 - Not Found
-
HTTP 501 - Not Implemented
Now let’s walk through the code
I have created a class PatientRepository to serve as data source for our service. You can find this class implementation at the end of this article.Let’s create the service
Add following two methods (OperationContract) in your ServiceContract interface
[OperationContract]
[WebGet(UriTemplate = "/{resourceType}")]
Stream GetResourceFeed(string resourceType);
GetResourceFeed will process request for given resource type and will return atom feed of all the resources of that type in xml or json format
[OperationContract]
[WebGet(UriTemplate = "/{resourceType}/@{id}")]
Stream GetResource(string resourceType, string id);
GetResource will process request for given resource type and logical id and will return that resource in either xml or json format
Now let’s implement GetResourceFeed operation
public Stream GetResourceFeed(string resourceType)
{
//return HTTP 501 if resource-type in uri is not patient
if (!resourceType.ToLower().Equals("patient"))
{
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotImplemented;
return new MemoryStream(Encoding.UTF8.GetBytes(resourceType + " This resource type not supported on this server"));
}
//create an instance of patient repository
PatientRepository pr = new PatientRepository();
//check Accept HTTP header
string strAccept = WebOperationContext.Current.IncomingRequest.Accept;
if (!string.IsNullOrEmpty(strAccept) && strAccept.Equals("application/json"))
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
//get patient feed in json format
return pr.getPatientResourceFeed(1);
}
else
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/atom"; //use application/atom+xml
//get patient feed in xml format
return pr.getPatientResourceFeed(0);
}
}
This operation identifies if the requested resource type is not Patient and returns HTTP 501 – Not Implemented response in that case.
It evaluates http header("Accept") and determines if the response should be xml or json.
It then calls "getPatientResourceFeed" method of PatientRepository class to get xml/json representation of Resource Feed
PatientRepository.getPatientResourceFeed() iterates the Patient Resource collection and creates a ResourceEntry for each Patient Resource.
All these resource entries are then added to the Bundle (available in HL7.Fhir.Instance.Support name space of C# reference implementation)
Then based on contentTypeCode received we will serialize Bundle as either json or xml.
Now, we will implement GetResource operation
public Stream GetResource(string resourceType, string id)
{
PatientRepository pr = new PatientRepository();
Stream response = null;
//check Accept HTTP header
string strAccept = WebOperationContext.Current.IncomingRequest.Accept;
if (!string.IsNullOrEmpty(strAccept) && strAccept.Equals("application/json"))
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
//get patient resource in json format
response = pr.getPatientResource(1, id);
}
else
{
WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml"; //use application/xml+fhir
//get patient resource in xml format
response = pr.getPatientResource(0, id);
}
if (response != null) // return patient resource if found
{
return response;
}
else // return HTTP 404 if resource not found
{
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain";
WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
return new MemoryStream(Encoding.UTF8.GetBytes("Resource: " + resourceType + "/@" + id + " not found"));
}
}
This operation evaluates http header("Accept") and determines if the response should be xml or json.
It then calls "getPatientResource"method of PatientRepository class. If this method returns null (resource not found) then we will return HTTP 404 – NotFound status code else it will return json/xml representation of Patient Resource
PatientRepository.getPatientResource() returns Patient Resource from Patient Resource Colletion based on @id parameter
It uses FhirSerializer.SerializeResourceAsXml() function to generate xml format output and FhirSerializer.SerializeResourceAsJson() to generate json format output.
These functions are available in C# reference implementation in HL7.Fhir.Instance.Serializers name space
With this we have created a RESTful FHIR service which exposes Patient Resource.
Test Your FHIR RESTful Service
Now deploy this service on IIS or just run it from Visual Studio.Use fiddler to make request to this service and examine the result. Try changing the element values in Patient Resource and see the effect. Also try to create a Provider Resource and use its URI in Patient Resource’s provider element.
This will help you getting started and implement other resources as well.
PatientRepository class
Please note: In patient resource, I have added provider uri as "http://hl7.org/fhir/profile/@iso-21090#name-qualifier". This should be the URI of Provider resource, but in this example we are only implementing Patient resource and we don’t have provider resource URI. public class PatientRepository()
{
List<Patient> lstPatient = null;
public PatientRepository()
{
lstPatient = new List<Patient>();
Patient p1 = new Patient()
{
Identifiers = new List<Identifier> { new Identifier() { Id = "111222333", Label = "SSN", System = new Uri("http://hl7.org/fhir/sid/us-ssn") } },
Details = new Demographics()
{
BirthDate = new FhirDateTime(1972, 11, 30),
Names = new List<HumanName> {
new HumanName() { Givens = new List<FhirString>() { "TestGiven1", "TestGiven2" },
Familys = new List<FhirString>() { "TestFamily1" } } },
Addresss = new List<Address>() { new Address() { Lines = new List<FhirString>() { "2222 Home Street" }, Use = Address.AddressUse.Home } },
Telecoms = new List<Contact>() { new Contact() { Use = Contact.ContactUse.Work, System = Contact.ContactSystem.Phone, Value = "123456" } }
},
Provider = new ResourceReference() { Type = new Code("Organization"), Url = new Uri("http://hl7.org/fhir/profile/@iso-21090#name-qualifier") },
Text = new Narrative()
{
Status = Narrative.NarrativeStatus.Generated,
Div = "<div xmlns='http://www.w3.org/1999/xhtml'>Patient SSN - 111222333</div>"
}
};
Patient p2 = new Patient()
{
Identifiers = new List<Identifier> { new Identifier() { Id = "111222444", Label = "SSN", System = new Uri("http://hl7.org/fhir/sid/us-ssn") } },
Details = new Demographics()
{
BirthDate = new FhirDateTime(1982, 02, 16),
Names = new List<HumanName> {
new HumanName() { Givens = new List<FhirString>() { "TestGiven2" },
Familys = new List<FhirString>() { "TestFamily2" } } },
Addresss = new List<Address>() { new Address() { Lines = new List<FhirString>() { "1111 Home Street" }, Use = Address.AddressUse.Home } },
Telecoms = new List<Contact>() { new Contact() { Use = Contact.ContactUse.Work, System = Contact.ContactSystem.Phone, Value = "444222" } }
},
Provider = new ResourceReference() { Type = new Code("Organization"), Url = new Uri("http://hl7.org/fhir/profile/@iso-21090#name-qualifier") },
Text = new Narrative()
{
Status = Narrative.NarrativeStatus.Generated,
Div = "<div xmlns='http://www.w3.org/1999/xhtml'>Patient SSN - 111222444</div>"
}
};
lstPatient.Add(p1);
lstPatient.Add(p2);
}
public Stream getPatientResourceFeed(int contentTypeCode)
{
//create a bundle
Bundle b = new Bundle();
//add all patient resources to bundle
for (int i = 0; i < lstPatient.Count; i++)
{
//create a resource entry from each Patient Resource
ResourceEntry re = new ResourceEntry();
re.Content = lstPatient[i];
re.Title = lstPatient[i].Details.Names[0].Givens[0].ToString();
//in this example, i am assigning increamental logical id
re.Id = new Uri("https://localhost:51456/service1.svc/patient/@" + (i+1).ToString() );
re.LastUpdated = new DateTimeOffset(DateTime.Now);
re.Published = new DateTimeOffset(DateTime.Now);
//in this example, i am assigning increamental logical id
re.SelfLink = new Uri("https://localhost:51456/service1.svc/patient/@" + (i + 1).ToString());
re.AuthorName = "Jayant Singh";
BundleEntry be = (BundleEntry)re;
//add BundleEntry to Bundle
b.Entries.Add(be);
Uuid uid = "urn:uuid:eca0e60d-0653-44cf-b797-8348fa8e14d7";
b.Id = new Uri(uid.ToString());
b.Title = "Patient Resource Collection";
b.LastUpdated = new DateTimeOffset(DateTime.Now);
}
//decide content type for bundle
if (contentTypeCode == 0)
{
//create bundle in xml format
using (var sw = new StringWriter())
{
using (var xw = XmlWriter.Create(sw))
{
b.Save(xw);
}
return new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
}
}
else
{
//create bundle in json format
using (var sw = new StringWriter())
{
using (JsonWriter jw = new JsonTextWriter(sw))
{
b.Save(jw);
}
return new MemoryStream(Encoding.UTF8.GetBytes(sw.ToString()));
}
}
}
public Stream getPatientResource(int contentTypeCode, string id)
{
if (id.Equals("1")) // check if URI referes to resource id @1. This is hard coded id for this example
{
if (contentTypeCode == 0) // if content type is xml
return new MemoryStream(Encoding.UTF8.GetBytes(FhirSerializer.SerializeResourceAsXml(lstPatient[0])));
else // content type is json
return new MemoryStream(Encoding.UTF8.GetBytes(FhirSerializer.SerializeResourceAsJson(lstPatient[0])));
}
else if (id.Equals("2")) // check if URI referes to resource id @2. This is hard coded id for this example
{
if (contentTypeCode == 0) // if content type is xml
return new MemoryStream(Encoding.UTF8.GetBytes(FhirSerializer.SerializeResourceAsXml(lstPatient[1])));
else // content type is json
return new MemoryStream(Encoding.UTF8.GetBytes(FhirSerializer.SerializeResourceAsJson(lstPatient[1])));
}
else //return null if id is not @1 or @2
return null;
}
}
All the resource types are defined in HL7.Fhir.Instance.Model name space of C# reference implementation.
Comments:
I appreciate honest (positive/negative) feedback.Source | Comment |
Ewout Kramer on 27th March 2013 use text/xml+fhir for the xml format of Resources, and application/json otherwise Furthermore, once you start transferring atom feeds, we use application/atom+xml or application/json (so this last one is the same as for individual resources!) |