Monday, July 23, 2012

Tutorial - Accessing REST bases SFDC chatter APIs on Windows 8

Windows 8 looks interesting! The hats (^) will take time to get used, but this by far looks the best upgrade targeted to devices that are mobile.

To understand this better, developed a small windows 8 metro app. 
The tasks were
1. Connect to saleforce.com for get a OAuth token
2. Pass the token to a REST chatter API
3. Get the result and parse it.

This tutorial should address the first two points

For OAuth token, windows 8 has a good mechanism using the "WebAuthenticationBroker". The following code uses the authentication broker to call Salesforce.com portal.

void TestRest::MainPage::Button_Click_1(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
//On click of button put the salesforce .com stuff to authenticate
String^ facebookURL = "https://login.salesforce.com/services/oauth2/authorize?client_id=";
//this is the key you get from salesforce...use your account specific key..
String^ clientID;
clientID = "3MVxxxxxxxxxxxxxxxxxxlCKNlWdhyd_JKQdo";
facebookURL += clientID + "&redirect_uri=https%3A%2F%2Flogin.salesforce.com%2Fservices%2Foauth2%2Fsuccess";
facebookURL += "&response_type=token&";
facebookURL += "&display=touch&";
try
{
auto startURI = ref new Uri(facebookURL);
auto endURI = ref new Uri("https://login.salesforce.com/services/oauth2/success");
create_task(WebAuthenticationBroker::AuthenticateAsync(WebAuthenticationOptions::None, startURI, endURI)).then([this](WebAuthenticationResult^ result)
{
String^ statusString;

switch (result->ResponseStatus)
{
case WebAuthenticationStatus::ErrorHttp:
statusString = "ErrorHttp: " + result->ResponseErrorDetail;
break;
case WebAuthenticationStatus::Success:
{
statusString = "Success";
//call the rest api here
m_CSfdcRESTMgr.setSfdcParameters(result->ResponseData);
}
break;
case WebAuthenticationStatus::UserCancel:
statusString = "UserCancel";
break;
}

//resultTextBox->Text += "\nStatus returned by WebAuth broker: " + statusString;

});
}
catch (Exception^ ex)
{
//resultTextBox->Text += "Error launching WebAuth " + ex->Message;
return;
}
}

this is for my developer account on Salesforce, you need to add your own client ID

m_CSfdcRESTMgr is a variable for the class CSfdcRESTMgr. This class manages the API code for Salesforce chatter.

on success SFDC sends you a string that contains 
1. OAuth token
2. Refresh token
3. Instance URL.

the code below parses the tokens, important aspect in the code is to remove escape characters. I am sure that part can be optimized further.


void CSfdcRESTMgr::setSfdcParameters(Platform::String ^ webRespone)
{
std ::wstring strReftoken;
strReftoken.assign(L"&refresh_token=");
wstring strInstanceURL = L"&instance_url=";
wstring strId = L"&id=";
wstring strIssuedAT = L"&issued_at=";
wstring strSignature = L"&signature=";
wstring strScope = L"&scope=";
try
{
std::wstring strData ;
strData.assign(webRespone->Data());
//access token
int i = strData.find('=');
++i; //move over =
int j = strData.find(strReftoken);
m_stSFInfo.access_token = strData.substr(i,j-i);
//escape the access token as well
int b = m_stSFInfo.access_token.find(L"%21");
m_stSFInfo.access_token.replace(b,3,L"!");
//refresh token
j += strReftoken.length();
i = strData.find(strInstanceURL);
m_stSFInfo.refresh_token = strData.substr(j,i-j);
i+= strInstanceURL.length();
//instance URL
j = strData.find(strId);
m_stSFInfo.instance_URL = strData.substr(i,j-i);
//m_stSFInfo.instance_URL = L"https://ap1.salesforce.com";
///special function starts to remove %2F with / and %3A with :
int a = m_stSFInfo.instance_URL.find(L"%2F");
m_stSFInfo.instance_URL.replace(a,3,L"/");
a =  m_stSFInfo.instance_URL.find(L"%2F");
m_stSFInfo.instance_URL.replace(a,3,L"/");
a =  m_stSFInfo.instance_URL.find(L"%3A");
if(a)
m_stSFInfo.instance_URL.replace(a,3,L":");
//
j+= strId.length();
//id
i = strData.find(strIssuedAT);
m_stSFInfo.id = strData.substr(j,i-j);
////
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
a =  m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id.replace(a,3,L"/");
a =  m_stSFInfo.id.find(L"%3A");
if(a)
m_stSFInfo.id.replace(a,3,L":");
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
a = m_stSFInfo.id.find(L"%2F");
m_stSFInfo.id .replace(a,3,L"/");
////
i += strIssuedAT.length();
//issued at
j = strData.find(strSignature);
m_stSFInfo.issuedAt = strData.substr(i,j-i);
j+= strSignature.length();
//signature
i = strData.find(strScope);
m_stSFInfo.signature = strData.substr(j,i-j);
i+= strScope.length();
//scope
j = strData.length();
m_stSFInfo.scope = strData.substr(i,j-i);
}
catch(Exception^ ex)
{
//GroupTextBox->Text += "error in parse" + ex->Message;

}
}

So now you have the token, pass that to the REST API.

std::wstring CSfdcRESTMgr::fireHTTPRequest(const wstring& methodName,const wstring& uri)
{

m_cancelHttpRequestSource = cancellation_token_source();
       auto token = m_cancelHttpRequestSource.get_token();
wstring oauthtoken = L"OAuth ";    
oauthtoken+= m_stSFInfo.access_token;
wstring uriToPass = m_stSFInfo.instance_URL + uri;
m_httpRequest = HTTPUtil::StartDownload(
        methodName, 
        uriToPass,
L"Authorization",
oauthtoken,
        token);
    // Create a continuation that shows the results on the UI.
    // Therefore, schedule the continuation to run on the current context.
    m_httpRequest.then([this, token](task<tuple<HRESULT, wstring>> previousTask) {
  
        if (token.is_canceled())
        {
            m_strResponse = L"The operation was canceled";
            cancel_current_task();
        }
        else
        {
            HRESULT hr = get<0>(previousTask.get());
            if (FAILED(hr))
            {
                m_strResponse = L"The operation failed";
                // TODO: Handle the error further.
            }
            else
            {
                auto text = ref new String(get<1>(previousTask.get()).c_str());
m_strResponse = text->Data();
            }
        }
    }, task_continuation_context::use_current());
return L" ";
}


void CSfdcRESTMgr::getLatestFeed()
{
wstring strURI = L"/services/data/v24.0/chatter/feeds/company/feed-items";
fireHTTPRequest(L"GET",strURI);
}

the response is got in  variable m_strResponse .

I have created a class to call the HTTP request and the associated callback

//implementation of the static method
// @@ToDo - what happen in case of multiple headers ??
task<tuple<HRESULT, wstring>>HTTPUtil::StartDownload(
const wstring& method, 
const wstring& uri,
const std::wstring& headerID,
const std::wstring& headerValue,
cancellation_token token)
{
//standard initiation in COM
ComPtr<IXMLHTTPRequest2>request;
HRESULT hr = CoCreateInstance(CLSID_XmlHttpRequest,nullptr,CLSCTX_INPROC,
IID_PPV_ARGS(&request));
if(FAILED(hr)) throw Exception::CreateException(hr);
//create callback
auto callback = Make<HttpRequestCallback>(request.Get(),token);

if(!callback) throw Exception::CreateException(hr);

//set the headers
//hr = request->SetRequestHeader(headerID.c_str(),);
//open and send the request
hr = request->Open(method.c_str(),uri.c_str(),callback.Get(),nullptr,nullptr,nullptr,nullptr);
if(FAILED(hr)) throw Exception::CreateException(hr);

hr = request->SetRequestHeader(headerID.c_str(),headerValue.c_str());
if(FAILED(hr)) throw Exception::CreateException(hr);

hr = request->Send(nullptr,0);
if(FAILED(hr)) throw Exception::CreateException(hr);

// Return a task that complets when the HTTP operation completes. 
    // We pass the callback to the continuation because the lifetime of the 
    // callback must exceed the operation to ensure that cancellation 
    // works correctly.
    return create_task(callback->GetCompletionEvent()).then(
        [callback](task<tuple<HRESULT, wstring>> previousTask)
    {
        return previousTask;
    });
}

the callback implementation
//callback constructor

HttpRequestCallback::HttpRequestCallback(IXMLHTTPRequest2* request,concurrency::cancellation_token token):
m_request(request),m_token(token),m_aborted(false)
{
//register the call back token
if(m_token != cancellation_token::none())
{
m_registrationToken = m_token.register_callback([this]()
{
m_aborted = true;
if(m_request != nullptr)
{
m_request->Abort();
}
m_completionEvent.set(make_tuple<HRESULT,wstring>(S_OK,wstring()));
});
}

}


task_completion_event<tuple<HRESULT, wstring>> const& HttpRequestCallback:: GetCompletionEvent()
{
return m_completionEvent;
}

// Called when the HTTP request is being redirected to a new URL.
IFACEMETHODIMP HttpRequestCallback::OnRedirect(IXMLHTTPRequest2*, PCWSTR) 
{
return S_OK;
}

// Called when HTTP headers have been received and processed.
IFACEMETHODIMP HttpRequestCallback::OnHeadersAvailable(IXMLHTTPRequest2*, DWORD, PCWSTR) 
{
return S_OK;
}

// Called when a portion of the entity body has been received.
IFACEMETHODIMP HttpRequestCallback::OnDataAvailable(IXMLHTTPRequest2*, ISequentialStream*) 
{
return S_OK;
}

// Called when the entire entity response has been received.
IFACEMETHODIMP HttpRequestCallback::OnResponseReceived(IXMLHTTPRequest2*, ISequentialStream* pResponseStream)
{
// Convert the response to Unicode wstring.
HRESULT hr;

// Holds the response as a Unicode string.
wstringstream ss;

while (true)
{
// Read the response.
ULONG cb;
char buffer[4096];
hr = pResponseStream->Read(buffer, sizeof(buffer), &cb);
if (FAILED(hr) || (cb == 0))
{
break; // Error or no more data to process, exit loop.
}
else
{
// First determine the size required to store the Unicode string.
int const sizeRequired = MultiByteToWideChar(CP_UTF8, 0, buffer, cb, nullptr, 0);
wchar_t* wstr = new(std::nothrow) wchar_t[sizeRequired + 1];
hr = wstr ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
// Convert the string.
MultiByteToWideChar(CP_UTF8, 0, buffer, cb, wstr, sizeRequired);
wstr[sizeRequired] = L'\0'; // Terminate the string.
ss << wstr; // Write the string to the stream.
delete[] wstr;
}
}
}

m_completionEvent.set(make_tuple<HRESULT, wstring>(
SUCCEEDED(hr) ? S_OK : hr, // dont' return S_FALSE
SUCCEEDED(hr) ? ss.str() : wstring()));
return hr;
}

// Called when an error occurs during the HTTP request.
IFACEMETHODIMP HttpRequestCallback::OnError(IXMLHTTPRequest2*, HRESULT hrError) 
{
// Set the completion event if the operation was not cancelled by the caller.
if (!m_aborted) 
{
m_completionEvent.set(make_tuple<HRESULT, wstring>(move(hrError), wstring()));
}
return S_OK;
}


HttpRequestCallback::~HttpRequestCallback()
    {
        // Unregister the callback.
        if (m_token != cancellation_token::none())
        {
            m_token.deregister_callback(m_registrationToken);
        }
    }

So here you go , you have a way of accessing REST based apis from chatter using windows 8

next blob - use the data to create a jazzy metro UI :)