Monday, October 29, 2012

Everest for Windows Phone 7–WCF Connector

Well, as I mentioned in my previous post, Everest is here for WP7. Currently we’re still sorting out some bugs but the first bit of code is available in the 1.1 branch on our public SVN.

So, what can you do with Everest for WP7? Well, pretty much anything you’d like to do with HL7v3. I made a sample application that will hit the MARC-HI client registry and shared health record to demonstrate how the WCF Connector works in the mobile version of Everest (it is slightly different than the full fledged desktop version).

We’re going to be making this app:

image

Create a simple Windows Phone 7 application in Visual Studio 2010 (like you normally would) and add these references to your project:

  • MARC.Everest.Phone.dll
  • MARC.Everest.Phone.Connectors.WCF.dll
  • MARC.Everest.Phone.Formatters.XML.ITS1.dll
  • MARC.Everest.Phone.Formatters.Datatypes.R1.dll
  • MARC.Everest.RMIM.CA.R020402.Phone.dll

First, we’ll need to create a ServiceReferences.ClientConfig file and set its build action to Content. In that file, we’ll configure the WCF client configuration (similar to the Everest desktop version):

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="hl7v3Binding" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="None" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://cr.marc-hi.ca:8080/cr" binding="basicHttpBinding"
                bindingConfiguration="hl7v3Binding"
                contract="MARC.Everest.Connectors.WCF.Core.IConnectorContract" name="ClientRegistry" />
        <endpoint address="http://shr.marc-hi.ca:8080/shr" binding="basicHttpBinding"
            bindingConfiguration="hl7v3Binding"
            contract="MARC.Everest.Connectors.WCF.Core.IConnectorContract" name="SHR" />
      </client>
    </system.serviceModel>
</configuration>

Note that WCF for the phone platform only supports basicHttpBinding and customBinding (no wsHttpBinding so SOAP 1.2 will need adjustments).


Next, we’ll initialize two connectors: one for the client registry and one for the shared health record. Because of the way that the WCF connectors work on Silverlight mobile (async only) you’re going to be using BeginSend and EndSend methods so these methods are fields for the Main.xaml.cs class:

private WcfClientConnector m_clientRegistryConnector;
private WcfClientConnector m_sharedHealthRecordConnector;

Next, we’re going to need a shared reference to the resolved patient identity received from the client registry, this is done via another field:

private MARC.Everest.RMIM.CA.R020402.PRPA_MT101104CA.IdentifiedEntity m_patientIdentification;

Since the WCF client connector will be sending the message asynchronously, meaning the actual sending is done on a separate thread. When the result callback is executed, it will be in the context of the thread that sent the message and not the thread that the UI is running on, we need to use the dispatcher to update the UI. Let’s declare a delegate to perform the UI update:

delegate void MessageSentDelegate(IGraphable connector);

Now, we can initialize the WCF connectors on the main page. I’ve done this in the constructor after InitializeComponent() is executed (which is hackish, but this is a tutorial after all).

// Constructor
public MainPage()
{
     InitializeComponent();
    m_clientRegistryConnector = new WcfClientConnector("endpointname=ClientRegistry");
    m_clientRegistryConnector.Formatter = new XmlIts1Formatter() { ValidateConformance = false };
    m_clientRegistryConnector.Formatter.GraphAides.Add(new DatatypeFormatter());
    m_clientRegistryConnector.Open();
    m_sharedHealthRecordConnector = new WcfClientConnector("endpointname=SHR");
    m_sharedHealthRecordConnector.Formatter = new XmlIts1Formatter() { ValidateConformance = false };
    m_sharedHealthRecordConnector.Formatter.GraphAides.Add(new DatatypeFormatter());
    m_sharedHealthRecordConnector.Open();
    ResolveClientIdentifiers();
}

Our resolve client identifiers code will execute a client registry lookup to translate our local identifier (I’m using 000-123-456) and get additional information about the client (their name, address, etc.). Here is the code:

private void ResolveClientIdentifiers()
{
    PRPA_IN101103CA instance = new PRPA_IN101103CA(
        Guid.NewGuid(),
        DateTime.Now,
        ResponseMode.Immediate,
        PRPA_IN101103CA.GetInteractionId(),
        PRPA_IN101103CA.GetProfileId(),
        ProcessingID.Production,
        AcknowledgementCondition.Always,
        new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Receiver(
            null,
            new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Device2(
                new II("1.3.6.1.4.1.33349.3.1.1.2", "CR")
            )
        ),
        new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Sender(
            new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Device1(
                new II("1.3.6.1.4.1.33349.3.1.1.20.4", "MARC-W1-1")
            )
        ),
        new MARC.Everest.RMIM.CA.R020402.MFMI_MT700751CA.ControlActEvent<MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ParameterList>(
            Guid.NewGuid(),
            PRPA_IN101103CA.GetTriggerEvent(),
            new MARC.Everest.RMIM.CA.R020402.MFMI_MT700711CA.Author(
                DateTime.Now
            ),
            new MARC.Everest.RMIM.CA.R020402.QUQI_MT120008CA.QueryByParameter<MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ParameterList>(
                Guid.NewGuid(),
                new MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ParameterList()
                {
                    ClientId = new List<MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ClientId>() {
                        new MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ClientId(
                            new II("1.3.6.1.4.1.33349.3.1.3.12", "000-123-456")
                        )
                    }
                }
            )
        )
        {
            EffectiveTime = new IVL<TS>(DateTime.Now)
        }
    );
    instance.ProfileId[0].Extension = "R02.04.02";


    instance.controlActEvent.Author.SetAuthorPerson(new MARC.Everest.RMIM.CA.R020402.COCT_MT090502CA.AssignedEntity(
        new MARC.Everest.RMIM.CA.R020402.COCT_MT090102CA.Organization(
            new II("1.2.840.114350.1.13.99998.8734"),
            "Good Health Hospital"
        )
    ));
     var retVal = m_clientRegistryConnector.BeginSend(instance, this.CrConnectorCallback, null);
           

}


You will notice that the callback for the BeginSend method is a callback delegate CrConnectorCallback. This method will be executed on the thread that the message was processed on, and will use the Dispatcher object to update the UI:

private void CrConnectorCallback(IAsyncResult result)
{
           
    var sendResult = this.m_clientRegistryConnector.EndSend(result);
    var res = this.m_clientRegistryConnector.Receive(sendResult);
    this.Dispatcher.BeginInvoke(
            new MessageSentDelegate(this.MessageSentResult),
            new object[] { res.Structure });
           
}

This callback executes the MessageSendResult delegate (of type MessageSentDelegate that we created earlier). This updates the UI to include the patient’s name in the Application Text area.

private void MessageSentResult(IGraphable message)
{
     if(message is PRPA_IN101104CA)
    {
        var msg = message as PRPA_IN101104CA;
        if(msg.Acknowledgement.TypeCode != AcknowledgementType.ApplicationAcknowledgementAccept)
            return;
        var rreg = msg.controlActEvent.Subject[0].RegistrationEvent.Subject.registeredRole;
        this.m_patientIdentification = rreg;
        this.ApplicationTitle.Text = rreg.IdentifiedPerson.Name[0].ToString("{FAM}, {GIV}");
        ExecuteGetSummary();
    }
}

The result of this will be an app that, a few seconds after loading, will display the name of patient 000-123-456 in the application text bar.


image


In my next post, I’ll show you how to get the clinical summary using COMT_IN100000CA messages with query continuation (a very useful construct on the phone).

No comments:

Post a Comment