Introduction
Necessity is the mother of invention, and Ivan's new foray into the startup world threatened to leave him hungry and broke. What he needed was customers for his new stock analysis application that regularly outperformed the market twofold. Is there a better source for market segment analysis than Dun & Bradstreet? But is there an easy way for him to get to that information and identify potential customers to approach them and make the sale?
Background
Enter Honey Badger [Lead Generation and] CRM. Available for both iPhone and Windows Phone, with Android version just a minor user interface work away, the app uses data from Dun and Bradstreet to find potential customers for Ivan's stroke of genius. But that's just the tip of app's functionality: with Honey Badger CRM, once Ivan identifies potential customers, he will record attempts to reach out and make sales to those customers on his mobile device. The app also has an element of gamification to it: every time a potential customer is contacted, a certain number of points (representing money, time, or both) is used. Supply of points is finite; Ivan must pick the right targets and make the sale before he runs out of points and dies of starvation.
We have put together a short video showcasing Honey Badger CRM:
We have also made source code available on GitHub:
Windows Phone: https://github.com/FabienLavocat/HoneyBadgerCRM
iOS: https://github.com/echuvyrov/HoneyBadgerCRM
You can also download code as .zip archive.
The rest of the article describes the technical implementation details of our solution. We hope that readers of codeproject.com will add a few tricks up their sleeves by reading this article.
Technical Implementation
To speed up mobile cross-platform development, the app uses Azure Mobile Services for all of the back-end processing. Dun and Bradstreet data is accessed from Azure Mobile Services and is not being called into from client application; a diagram below illustrates a high-level architecture of the application we built.
There are three separate pieces of technology that we touched on when implementing this application: Azure Mobile Services, iOS development and Windows Phone programming. Below, we highlight the most important parts of our solution for each of those technologies.
Windows Azure Mobile Services
Windows Azure Mobile Services (WAMS) allows you to setup the backend or your mobile application quickly, without provisioning separate servers, installing and managing database engine and worrying about scalability. To speed up cross-platform development, we relied heavily upon the features and the tooling provided by WAMS.
Accessing D&B data with Custom API
Custom API on WAMS enables server-side scripting with JavaScript (node.js running under the hood). We have introduced a Custom API layer to call into D&B Developer sandbox; mobile clients call into this layer instead of communicating with D&B Sandbox data directly. There are at least two reasons for doing this:
- We avoid distributing security credentials needed to access D&B data to mobile devices, which can be easily reverse-engineered.
- We encapsulate common data access and business logic into a single entry point instead of spreading it across the devices.
The code below is one of the actual Custom API endpoints we use and it retrieves Company Details given the DUNS identifier passed in. Similar code could be written to access other Azure Data Marketplace stores (not just D&B Sandbox) and other OData providers. Once saved, Custom API could be accessed at the following endpoint: http://<your-wams-app-name>.azure-mobile.net/api/yourapiname.
exports.get = function (req, res) {
var http = require('https');
var username = '<YOUR USERNAME>',
password = '<YOUR PASSWORD>';
var body = '';
var searchParams = '';
if(req.query.DUNSNumber) {
searchParams = "DUNSNumber eq '" + req.query.DUNSNumber + "'";
}
var dnb = {
host: "api.datamarket.azure.com",
path: "/DNB/DeveloperSandbox/v1/Firmographics?$format=JSON&$filter=" + encodeURIComponent(searchParams),
headers: {
'Authorization': 'Basic ' + new Buffer(username + ':' + password).toString('base64')
}
};
http.request(dnb, function (dnb_response) {
dnb_response.on('data', function(chunk){
console.log("Got chunk: " + chunk);
body += chunk;
});
dnb_response.on("end", function(e){
res.send(200, body);
});
}).on('error', function(e) {
console.log("Got error: " + e.message);
}).end();
}
Saving customer data
Saving customer data into WAMS is straightforward, especially given that the data does not (and perhaps should not) be in a relational format. You will learn about the Save operation in the Windows Phone and iOS sections below; the important and very convenient piece that WAMS bring to the table is the introduction of custom scripts that could be executed server-side before each read, insert, update, or delete. For example, the following code snippet illustrates how before inserting into the users table, we check whether the user already exists; with the code below, new users also get 100 points each to start out campaigning with.
function insert(item, user, request) {
var userTable = tables.getTable('user');
userTable.where({ UserId: user.userId })
.read({ success: checkIfUserExists });
function checkIfUserExists(results) {
if (results.length === 0) {
console.log('New User %s.', user.userId);
item.UserId = user.userId;
item.Points = 100;
request.execute();
}
else {
request.respond(statusCodes.OK, results[0]);
}
}
}
Windows Phone Client
There are some necessary steps to setup Windows Phone for WAMS access. To effortlessly work with WAMS, we need to add the following NuGet packages to the project:
Then, if you did not do so yet, download and install the Windows Azure Mobile Services SDK.
Finally, create or open your Windows Phone 8 project in Visual Studio and add the following code into the App.xaml.cs:
public static readonly MobileServiceClient MobileService =
new MobileServiceClient("https://[address].azure-mobile.net/", "[code]");
The security information for the {code] part above can be found on the dashboad of the Azure Mobile Services project you created.
Retrieving potential customers
As we have described above, all access to D&B data happens from the Custom API on Azure Mobile Services. To retrieve customer data, Windows Phone client needs to call into that API, with the proper parameters passed in on the query string. After the search query string is built via simple concatenation, the following code calls Custom API to retrieve a list of potential customers:
private static async Task DownloadResultAsync(string url, NetworkCredential credentials)
{
var client = new WebClient { Credentials = credentials };
var result = await client.DownloadStringTaskAsync(new Uri(url));
return JsonConvert.DeserializeObject(result);
}
After prospective customers have been retrieved, we map them on the Windows Phone device.
Saving data to Azure Mobile Services
Saving data to Azure Mobile Services is also easy: first, we'll declare an object that will contain the data to be saved:
[JsonObject]
public class Campaign
{
[JsonProperty]
public int Id { get; set; }
[JsonProperty]
public string User { get; set; }
[JsonProperty]
public string Name { get; set; }
}
Then, we invoke methods from WAMS SDK to push the data to the cloud
private async Task SaveCampaign()
{
Campaign c = new Campaign
{
User = App.MobileServicesUser.UserId,
Name = campaign.Name,
};
try
{
IMobileServiceTable campaignTable = App.MobileService.GetTable();
await campaignTable.InsertAsync(c);
NavigationService.Navigate(new Uri("/NextPage.xaml", UriKind.RelativeOrAbsolute));
}
catch (Exception)
{
MessageBox.Show("An error happened. Please try again.");
}
}
iOS Client
We have also built a native iOS client with functionality identical to Windows Phone. Since a lot of logic has been encapsulated on Windows Azure Mobile Services, we were able to build the app fairly quickly. Below are some of the highlights on leveraging WAMS from iOS.
Azure Mobile Services offers a set of starter projects that include a fully-functional ToDo app. If you are starting a brand new project with WAMS, it may be most convenient to download this project and start modifying it. If, however, you have an existing application you'd like to port to WAMS, download and install the Windows Azure Mobile Services SDK. Then, add WindowsAzureMobileServices
framework to your project.
The most convenient way to communicate with WAMS is to have a single class handling all of the data manipulation and Azure-level work. Downloadable starter project already contains this class; to create this class for an existing project, add a new NSObject
class and call it honeyBadgerService
. Inside the honeyBadgerService.h file, make sure you have the following #include
statement and property declarations:
#import <WindowsAzureMobileServices/ WindowsAzureMobileServices.h>
@property (nonatomic, strong) NSArray *companies;
@property (nonatomic, strong) NSArray *campaigns;
@property (nonatomic, strong) MSClient *client;
@property (nonatomic, copy) QSBusyUpdateBlock busyUpdate;
@property (nonatomic, strong) MSTable *table;
To complete the setup and to communicate with WAMS, we added the following code inside the honeyBadgerService.m
:
+ (honeyBadgerService *)defaultService
{
static honeyBadgerService* service;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
service = [[honeyBadgerService alloc] init];
});
return service;
}
-(honeyBadgerService *)init
{
self = [super init];
if (self)
{
MSClient *client = [MSClient clientWithApplicationURLString:@"<YOUR WAMS URL>"
applicationKey:@"<YOUR KEY>"];
self.client = [client clientWithFilter:self];
self.table = [_client tableWithName:@"campaign"];
self.companies = [[NSMutableArray alloc] init];
self.campaigns = [[NSMutableArray alloc] init];
self.busyCount = 0;
}
return self;
}
With these setup steps complete, we are ready to retrieve and save data to the server.
Retrieving customer campaigns
One of the screens in our app shows all the contacts and campaigns we tried to establish with a given customer. The code snippet below how to get to that data. In short, we create an NSPredicate
to retrieve the data we are interested in and use methods from Azure Mobile Services SDK to get the data from the server.
- (void)getCampaignsForDUNSNumber:(NSString*)dunsNumber completion:(QSCompletionBlock)completion
{
NSString* searchString = [@"DUNSNumber == " stringByAppendingString:dunsNumber];
NSPredicate * predicate = [NSPredicate predicateWithFormat:searchString];
[self.table readWithPredicate:predicate completion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
[self logErrorIfNotNil:error];
campaigns = [results mutableCopy];
completion();
}];
}
Retrieving potential customers is accomplished in a manner identical to that of Windows Phone: we build out query string and call into the Custom API on Azure Mobile Services and process the result set retrieved:
- (NSDictionary *)getTargets
{
NSString* query = [@"http://dnb-crm.azure-mobile.net/api/firmographics?City=" stringByAppendingString:dnbCity];
query = [query stringByAppendingString:@"&State="];
query = [query stringByAppendingString:dnbState];
query = [query stringByAppendingString:@"&IndustryCode="];
query = [query stringByAppendingString:[self getIndustryCode]];
query = [query stringByAddingPercentEscapesUsingEncoding:
NSASCIIStringEncoding];
NSData *jsonData = [[NSString stringWithContentsOfURL:[NSURL URLWithString:query] encoding:NSUTF8StringEncoding error:nil] dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *results = jsonData ? [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves error:&error] : nil;
if (error) NSLog(@"[%@ %@] JSON error: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), error.localizedDescription);
return results;
}
Similar to Windows Phone app, we map potential customers to see how close they are to us.
Saving data to Azure Mobile Services
Saving data into Azure Mobile Services from iOS client is almost as easy as from Windows Phone: we pass in an NSDictionary
with key/value pairs corresponding to columns/column values in the data store. We then invoke the insert method on the WAMS table. The table will expand to contain new columns if they appear in the NSDictionary
passed in; this malleable nature is one of the keys to getting apps on WAMS going quickly.
-(void)addItem:(NSDictionary *)item completion: (QSCompletionWithIndexBlock)completion
{
[self.table insert:item completion:^(NSDictionary *result, NSError *error)
{
[self logErrorIfNotNil:error];
NSUInteger index = [campaigns count];
[(NSMutableArray *)campaigns insertObject:result atIndex:index];
completion(index);
}];
}
It would make for a pretty long article to cover every implementation detail of our solution. Please take a look at the source code for implementation clarifications and also feel free to contact us.
Now this Honey Badger (CRM) is ready for battle!
History
Initial release (version 1.0) - July 30th, 2013.