Getting back to developing applications for BlackBerry. My work needed an application for collecting sales leads at production shows. Apparently you can rent the scanners from the show providers, but renting them and getting the data is a bit expensive year after year. One of the Marketing group folks came to me and asked if I could make an app that would eliminate the need for paying for the show services. I thought it would be great to write a new app and started at it. However, we ran into some basic issues with the scanning PDF 417 codes, which made me have to switch from an ActionScript application to a BB10 Native application. Not that I didn’t want to learn Native coding for BB10, but there were a lot of things going on at work and this was a bit of a time crunch. Anyway, I ran into some issues while creating the application and here is what I did to get things solved.
This post is about an issue I had with consuming .Net web services in BB10.
BlackBerry provides example apps in GitHub, including one about consuming SOAP XML. This was my starting point for consuming the web service.
After creating a basic .Net web service that calls a stored procedure on a SQL server, the first step is to make sure it works, which of course, doesn’t tell you an important step; checking for null values. When you test a .Net web service from the asmx page, it will pass in non-null values to your web service. In doing so, it lulls you into thinking that everything will work fine when the web service is called elsewhere and null values are passed in. When I attempted to call the web service from the BB10 code, I would receive the error back “Object reference not set to an instance of an object“. It took me a while to determine that the error was indeed coming from the .Net web service, then it was the matter of getting it fixed, which is very simple.
For each inbound variable, check to see if the value is null or empty or of zero length. Then do an action based on the if statement. In this example, DeviceEmail is the parameter for the web service that is passed in and @IN_DeviceEmail is the stored procedure parameter. If the input value is not passed in, then the stored procedure value is set to “No Device Email“.
if (DeviceEmail == null || DeviceEmail == String.Empty || DeviceEmail.Trim().Length == 0) { cmd.Parameters.Add(new SqlParameter("@IN_DeviceEmail", "No Device Email")); } else { cmd.Parameters.Add(new SqlParameter("@IN_DeviceEmail", DeviceEmail)); }
Of course, the check for null should be based on the input type. I set my web service input types to String in order to make sure things were working correctly.
Another issue is getting back a simple confirmation from .Net that the web service has processed. If you are doing a select in .Net, it is simple enough to convert the dataset to xml. But, a basic Non-query Execute will only give you back an integer for the result. To make it easier to read things when it comes back, you can build your own xml output for the result. In this case, I simple wanted to send back a “Success” or “Failure” message, plus the ID of the sent item. This is accomplished by building an xml document and adding document elements to it. Also, I attempted to modify the xml type by adding a declaration to set it to “ISO-8859-1“. I’m not sure if that helped much, because I figured out how to read the xml without it and the result still read as “UTF-8“.
[WebMethod(CacheDuration = 0, Description = "Inserts a Sales Lead.", EnableSession = false)] [XmlInclude(typeof(String))] public XmlDocument insertSalesLead(String DeviceEmail, myparams, ....) { XmlDocument xmlD = new XmlDocument(); XmlDeclaration declaration = xmlD.CreateXmlDeclaration("1.0", "ISO-8859-1", null); xmlD.AppendChild(declaration); xmlD.LoadXml(" "); XmlElement resultElem = xmlD.CreateElement("result"); // start SQL connection // Call Stored Procedure SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Web.DAL.Properties.Settings.ConnectionString"].ToString()); try { conn.Open(); SqlCommand cmd = new SqlCommand("myStoredProcedure", conn); cmd.CommandType = CommandType.StoredProcedure; // Make the Lead value an int. If value is null, then set to 999 int iLeadID = 0; if (LeadID == null || LeadID == String.Empty || LeadID.Trim().Length == 0) { iLeadID = 999; } else { iLeadID = int.Parse(LeadID); } xmlD.DocumentElement.AppendChild(idElem); XmlText xLeadText = xmlD.CreateTextNode(iLeadID.ToString()); xmlD.DocumentElement.LastChild.AppendChild(xLeadText); if (DeviceEmail == null || DeviceEmail == String.Empty || DeviceEmail.Trim().Length == 0) { cmd.Parameters.Add(new SqlParameter("@IN_DeviceEmail", "No Device Email")); } else { cmd.Parameters.Add(new SqlParameter("@IN_DeviceEmail", DeviceEmail)); } ..... commandInt = cmd.ExecuteNonQuery(); if (commandInt != 0) { resultText = "Success"; } } finally { // Close the connection if (conn != null) { conn.Close(); } } xmlD.DocumentElement.AppendChild(resultElem); XmlText xResultText = xmlD.CreateTextNode(resultText); xmlD.DocumentElement.LastChild.AppendChild(xResultText); return xmlD; } //
The results look like this
<?xml version="1.0" encoding="UTF-8"?> <insertResults> <id>999</id> <result>Success</result> </insertResults>
The next part is getting BB10 to use the web service. This is actually straight forward, once you realize where your other errors were coming from.
It starts with the function to call the web service. I’m still working on separating the web service class from the main application class. But, for this example, I am using the main application class. The hostServer, action and end point can all be found in the WSDL of the web service. By creating the 3 variables at the start, it will be easier for me going forward to reuse the code and just plugin the appropriate Action and/or web service. One of the keys was to get the host from the host server URL in the setHost. Simply done by using QUrl(hostServer).host(). The other issue was making sure that I used the correct method to build the web service. In this case, I didn’t need a structure, I just added each of parameter values making sure to use QtSoapSimpleType and QtSoapQName to ensure that the values were in the correct type.
void MainAppClass::insertSalesLead(const int &leadIDKey) { const QString hostServer = "http://myServer/WebServices"; const QString serverAction = "insertSalesLead"; const QString endPointURI = "/WebServices/MyWebServicesPage.asmx"; QtSoapMessage request; request.setMethod(QtSoapQName(serverAction, hostServer)); // Open Database and get data from query QSqlDatabase database = QSqlDatabase::database(); if (database.open() == false) { qDebug() << "database failed to open"; } else { qDebug() << "database open"; } QSqlQuery query(database); const QString sqlCommand = "SELECT employee..." " FROM leads " " WHERE leadID = :leadID"; query.prepare(sqlCommand); query.bindValue(":leadID", leadIDKey); if (query.exec()) { query.first(); // Add each paramter request.addMethodArgument( new QtSoapSimpleType(QtSoapQName("AssociateEmail"), query.value(0).toString())); ... add rest of the parameters } else { qDebug() << "SQL execute failed! " << query.lastError(); } //set value and submit m_soap.setAction(hostServer + "/" + serverAction); m_soap.setHost(QUrl(hostServer).host()); m_soap.submitRequest(request, endPointURI) }
After the request is setup, then it is a matter of handling the response. Since the SOAP XML example uses a different type of output, I needed to figure out how to parse the XML I was creating. The solution was to create an xml document and then go through the nodes to find the one I needed. Then get the element out of that node to check the value.
void MainAppClass::onServiceResponse() { // Get the response, check for error. const QtSoapMessage& response = m_soap.getResponse(); qDebug() << "Service Response."; if (response.isFault()) { m_error = tr("Query failed: %1").arg(response.faultString().value().toString()); emit statusChanged(); m_active = false; emit activeChanged(); emit complete(); return; } // Extract the return value from this method response, check for errors. const QtSoapType& responseValue = response.returnValue(); if (!responseValue.isValid()) { m_error = tr("Query failed: invalid return value"); emit statusChanged(); m_active = false; emit activeChanged(); emit complete(); return; } // Convert to XML QXmlStreamReader xml(response.toXmlString()); QDomDocument doc; QXmlInputSource is; is.setData(response.toXmlString()); doc.setContent(is.data()); // Get Result elements QDomNodeList resultNode = doc.elementsByTagName("result"); // Get value QString resultValue; if (resultNode.size() > 0) { resultValue = resultNode.at(0).toElement().text(); //qDebug() << "result : " << resultNode.at(0).toElement().text(); } // Get Result elements QDomNodeList idNode = doc.elementsByTagName("id"); // Get value QString idValue; if (idNode.size() > 0) { idValue = idNode.at(0).toElement().text(); //qDebug() << "id : " << idNode.at(0).toElement().text(); } if(resultValue.compare("Success") == 0) { // do final action } else { // Update record to sent warnAlert("Lead Record Was Not Saved", "Data Save Alert"); } emit statusChanged(); m_active = false; emit activeChanged(); emit complete(); }
And that (for the most part) is how you consume a .Net web service with BB10 Native. It took me a little too long to figure out some basic issues, so I am assuming I’ll be able to setup new web service function a lot quicker in the future.
About DeanLogic
Dean has been playing around with programming ever since his family got an IBM PC back in the early 80's. Things have changed since BASICA and Dean has dabbled in HTML, JavaScript, Action Script, Flex, Flash, PHP, C#, C++, J2ME and SQL. On this site Dean likes to share his adventures in coding. And since programming isn't enough of a time killer, Dean has also picked up the hobby of short film creation.