By Philippe Leefsma
Here is the 10th episode of my long lasting series of posts about creation of a cloud-based viewer. The concept is mainly a starting point in order to illustrate and experiment various cloud-centered technologies.
After having played a bit with JavaScript to write Windows Store Apps in the past few weeks, my target was this time to create a JavaScript Windows 8 App client App for my WCF viewer service.
Since my previous posts, I was as well gathering some more experience on Restful web-services, so I decided to update every components in my sample project to the state-of-the-art REST/.Net technologies.
I – WCF REST Web-Service
I enhanced the web-service interface so it supports the 3 most useful http verbs: GET, POST, DELETE:
[ServiceContract]
interface IAdnCloudViewerSrv
{
// Returns list of all cloud hosted models
[OperationContract]
[WebInvoke(
Method = "GET",
UriTemplate = "/Models",
ResponseFormat = WebMessageFormat.Json)]
ModelInfo[] GetModels();
// Returns model data for a specific model
[OperationContract]
[WebInvoke(
Method = "GET",
UriTemplate = "/Model/{modelId}",
ResponseFormat = WebMessageFormat.Json)]
byte[] GetModel(
[MessageParameter(Name = "modelId")]
string modelId);
// Uploads model data as stream to server
[OperationContract]
[WebInvoke(
Method = "POST",
UriTemplate = "/UploadModel",
BodyStyle = WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
bool UploadModel(Stream modelData);
// Deletes specific model
[OperationContract]
[WebInvoke(
Method = "DELETE",
UriTemplate = "/Model/{modelId}",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
bool DeleteModel(
[MessageParameter(Name = "modelId")]
string modelId);
}
If GET and DELETE are rather straightforward to handle, the POST is a bit more tricky, especially that I wanted to be able to stream raw data from the console client to the server.
-
This post was a great help in order to get it right. Because I wanted to be able to upload large pictures from my client to the server, timeouts and quotas have to be customized a bit in your web.config file.
Here is how my binding configuration looks like:
<webHttpBinding>
<binding
name="webHttpBindingWithJson"
transferMode="StreamedRequest"
receiveTimeout="00:05:00"
sendTimeout="00:05:00"
openTimeout="00:05:00"
closeTimeout="00:05:00"
maxBufferSize="2147483647"
maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647">
<readerQuotas
maxDepth="200"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxNameTableCharCount="2147483647"/>
</binding>
</webHttpBinding>
The client side is as well tricky because the ContentType of your http request needs to be set to "text/plain" in order not to be rejected by WCF framework when the server receives it.
byte[] data = mu.ToByteArray();
HttpWebRequest request = WebRequest.Create(
"http://" + _hostAddress +
"/AdnCloudViewerSrv.svc/rest/UploadModel")
as HttpWebRequest;
request.Method = WebRequestMethods.Http.Post;
request.ContentType = "text/plain";
request.ContentLength = data.Length;
using (var outputStream = request.GetRequestStream())
{
outputStream.Write(data, 0, data.Length);
outputStream.Flush();
outputStream.Close();
using (var response = await request.GetResponseAsync())
{
using (StreamReader reader = new StreamReader(
response.GetResponseStream()))
{
string jsonMsg = reader.ReadToEnd();
var res = await JsonConvert.DeserializeObjectAsync
<bool>(jsonMsg);
return res;
}
}
}
II – REST Client with .Net 4.5 asynchronous functionalities
.Net 4.5 is bringing a great deal of enhancements when it comes to asynchronous and parallel programing. No more need to use complex delegate syntax in order to perform asynchronous calls to your methods. Stephen was already blogging about it. All you need to do now is sign your method as an “asnyc Task” and use await keyword to perform any blocking call.
As example here is the client code to refresh the list of models present on the server. It performs an asynchronous http request and update the UI afterward. While waiting for the reply from the server, the UI remains responsive and the code itself is pretty elegant and compact. I highlighted the asynchronous code in yellow:
public ViewerClientForm()
{
InitializeComponent();
_modelIdNodeMap = new Dictionary<string, TreeNode>();
_tvModels.AfterSelect += new TreeViewEventHandler(
tvModels_AfterSelect);
// runs our task asynchronously
var task = RefreshCloudModels();
Cursor = Cursors.WaitCursor;
}
///////////////////////////////////////////////////////////////////////
// Refresh Models TreeView
//
///////////////////////////////////////////////////////////////////////
private async Task RefreshCloudModels()
{
_tvModels.Nodes.Clear();
_modelIdNodeMap.Clear();
TreeNode root = _tvModels.Nodes.Add(
"_cloudModelsKey", "Cloud Models", 0, 0);
try
{
HttpWebRequest request = WebRequest.Create(
"http://" + _hostAddress +
"/AdnCloudViewerSrv.svc/rest/Models")
as HttpWebRequest;
// perform async http request
using (WebResponse response =
await request.GetResponseAsync())
{
// retrieve response
using (StreamReader reader = new StreamReader(
response.GetResponseStream()))
{
string jsonMsg = reader.ReadToEnd();
// deserialize json response using Json.Net lib
var infos = await JsonConvert.DeserializeObjectAsync
<List<ModelInfo>>(jsonMsg);
// update UI with retrieved modelInfos
if (infos != null)
{
foreach (ModelInfo info in infos)
{
if (!_modelIdNodeMap.ContainsKey
(info.ModelId))
{
TreeNode node =
root.Nodes.Add("", info.ModelId, 1, 1);
node.Tag = info;
_modelIdNodeMap[info.ModelId] = node;
}
}
root.Expand();
Cursor = Cursors.Default;
}
}
}
}
catch (Exception ex)
{
Cursor = Cursors.Default;
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
III – Windows Store App client with JavaScript/HTML5
The second client I created for this sample project is a Windows Store App written in JavaScript/HTML5. Instead of starting from scratch, I reused a suitable template project, the “Split App”.
-
The new WinJs namespace provides a convenient function that makes asynchronous http request pretty painless: WinJs.xhr. Below is how I used that method to perform my web-service calls to retrieve my model list and download data for a given model:
doCmdRefresh: function () {
var listView = document.getElementById("modelList").winControl;
listView.itemDataSource = null;
var items = null;
WinJS.xhr({
url: "http://" +
"adnviewer.cloudapp.net" +
"/AdnCloudViewerSrv.svc/rest/Models"
}).
then(
function completed(request) {
var modelInfos = JSON.parse(request.responseText);
var dataList = new WinJS.Binding.List(modelInfos);
//this._items items = dataList;
listView.itemDataSource = dataList.dataSource;
},
function error(request) {
// handle error conditions.
},
function progress(request) {
// report on progress of download.
});
},
getModel: function (modelInfo) {
function byteArrayToBase64(data) {
var str = data.reduce(
function (a, b) {
return a + String.fromCharCode(b) }, '');
return btoa(str).replace(/.{76}(?=.)/g, '$&\n');
}
var img = document.querySelector(".article-image");
WinJS.xhr({
url: "http://" +
"adnviewer.cloudapp.net" +
"/AdnCloudViewerSrv.svc/rest/Model/" + modelInfo.ModelId
}).
then(
function completed(request) {
var modelData = JSON.parse(request.responseText);
img.src = "data:image/" +
modelInfo.FileExt.substring(1) +
";base64," + byteArrayToBase64(modelData);
img.style.display = "block";
},
function error(request) {
// handle error conditions.
},
function progress(request) {
// report on progress of download.
});
},
I tweaked a bit the template to bend it to a similar client than what we have in .Net and here is a picture of the final result:
The complete sample project is available below to download.