端到端真实世界 BlackBerry 应用程序,第 6 部分





5.00/5 (3投票s)
如何创建真实世界的 BlackBerry 应用程序。
引言
在本系列的第六篇端到端 BlackBerry 应用程序演练中,我将涵盖以下主题:
- 创建 .NET HTTP Handler,用于发送和接收来自手持设备应用程序的信息
- 从我们的 HTTP Handler 调用服务器端业务逻辑服务
- 从手持设备应用程序发起请求并接收数据
虽然本系列中 之前的文章 讨论了如何创建将在手持设备上安装的 Java 应用程序,但今天我将处理服务器端模块。让我们再次审视我们的基本构件:
服务器端模块包括一个 .NET HTTP Handler,一组包含业务逻辑和数据访问代码的类库,以及一个将作为我们知识库应用程序文章存储库的 SQL Server 数据库。
创建 HTTP Handler
HTTP Handler 的任务是从手持设备发起的 HTTP 请求中提取信息,确定需要执行的操作类型,并要求业务逻辑层执行这些操作。它还将业务逻辑产生的結果附加到 HTTP 响应中,并将它们发送到手持设备应用程序。
为了让手持设备应用程序和 Handler 能够相互通信,手持设备应用程序可能发送的所有命令都需要在 Handler 中进行定义,就像它们在手持设备中定义一样。
// The commands the handheld can send us.
private const int CMD_SEARCH_ARTICLES = 1;
private const int CMD_GET_ARTICLES_BY_TAG = 2;
private const int CMD_GET_TAG_COUNTS = 3;
标识每个请求参数的键也需要进行定义。
// The request keys.
private const string KEY_COMMAND = "cmd";
private const string KEY_SEARCH_PHRASE = "sp";
private const string KEY_TAG = "tg";
KEY_SEARCH_PHRASE
标识在发起搜索现有文章的请求时承载搜索短语的请求参数。KEY_TAG
标识在请求检索给定标签的文章时承载标签名称的参数。
确定做什么,就是检查由 KEY_COMMAND
键标记的请求参数。
string commandString = request.Form[KEY_COMMAND];
if (!int.TryParse(commandString, out currentCommand))
{
// Bail if we don't receive a numeric command.
return;
}
articlesServer = Shared.GetArticlesServer(context);
switch (currentCommand)
{
case CMD_SEARCH_ARTICLES:
string searchPhrase = request.Params[KEY_SEARCH_PHRASE];
responseString = SearchArticles(searchPhrase);
break;
case CMD_GET_ARTICLES_BY_TAG:
string tag = request.Params[KEY_TAG];
responseString = GetArticlesForTag(tag);
break;
case CMD_GET_TAG_COUNTS:
responseString = GetTagCounts();
break;
default:
return;
}
调用业务逻辑服务
今天我将不详细介绍业务逻辑和数据访问层的构建,因为它们在我之前的 端到端 ExtJS 应用程序 系列中已经涵盖过了。不过,值得注意的是,这些层支持将文章存储在文件或 SQL Server 数据库中。今天文章的代码下载默认使用基于文件的存储,但你可以运行包含的 SQL 脚本来安装数据库,并通过更改 Web 应用程序的配置文件将其指向业务层。
在 HTTP Handler 中,SearchArticles()
、GetArticlesForTag()
和 GetTagCounts()
只是将请求转发给业务逻辑层。
private string SearchArticles(string searchPhrase)
{
Debug.Assert(searchPhrase != null);
if (null == searchPhrase)
{
ExceptionPolicy.HandleException(
new ArgumentNullException("searchPhrase"),
Shared.KB_EXCEPTION_PLCY);
}
string result = string.Empty;
try
{
List<IArticle> articles =
articlesServer.GetArticlesForSearchPhrase(searchPhrase);
if (null == articles) return string.Empty;
result = SerializeArticlesList(articles);
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
}
return result;
}
private string GetArticlesForTag(string tag)
{
Debug.Assert(tag != null);
if (null == tag)
{
ExceptionPolicy.HandleException(
new ArgumentNullException("tag"),Shared.KB_EXCEPTION_PLCY);
}
string result = string.Empty;
try
{
List<IArticle> articles = articlesServer.GetArticlesForTag(tag);
if (null == articles) return string.Empty;
result = SerializeArticlesList(articles);
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
}
return result;
}
private string GetTagCounts()
{
StringBuilder sb = new StringBuilder("");
try
{
Dictionary<string, int> tagCounts = articlesServer.GetTagCounts();
if (null != tagCounts && tagCounts.Count > 0)
{
foreach (KeyValuePair<string, int> tagStat in tagCounts)
{
sb.Append(string.Format("{0}^~^{1}~^~",
tagStat.Key, tagStat.Value));
}
}
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, Shared.KB_EXCEPTION_PLCY);
}
return sb.ToString();
}
在业务逻辑结果传递给 Handler 后,我需要根据编写手持设备代码时建立的格式约定对结果进行序列化。例如,这个例程会将文章列表转换为一个字符串,该字符串将作为 HTTP 响应的一部分发送到手持设备。
private string SerializeArticlesList(List<IArticle> articles)
{
Debug.Assert(articles != null);
if (null == articles)
{
ExceptionPolicy.HandleException(
new ArgumentNullException("articles"),
Shared.KB_EXCEPTION_PLCY);
}
StringBuilder sb = new StringBuilder("");
string body;
foreach (IArticle article in articles)
{
// Decode the html characters in the body of the article.
body = HttpUtility.HtmlDecode(HttpUtility.UrlDecode(article.Body));
// Strip the html characters
// (We're using a RichTextField on the handheld,
// which isn't capable yet of displaying html.
body = Regex.Replace(body, @"<(.|\n)*?>", string.Empty);
sb.Append(string.Format("{0}^~^{1}^~^{2}^~^{3}^~^",
article.Id.ToString(), article.Title,
article.DateCreated.ToShortDateString(), article.Author));
sb.Append(string.Format("{0}^~^{1}~^~", article.Tags, body));
}
return sb.ToString();
}
你有没有注意到上面代码中我还在移除文章正文中的 HTML 字符?我必须这样做,因为我用来在手持设备上显示文章正文的字段无法渲染 HTML。
现在剩下要做的就是将数据发送到手持设备了。
response.ContentType = "text/plain";
int contentLength = response.Output.Encoding.GetByteCount(responseString);
response.AppendHeader("content-length", contentLength.ToString());
response.Write(responseString);
response.End();
好吧,我知道服务器端代码的演练进行得很快。我鼓励你下载代码并进行查看,以了解所有细节。此外,阅读端到端 ExtJS 应用程序的帖子可以让你更深入地了解业务逻辑和数据访问层。
从手持设备应用程序发起请求并接收数据
让我们回到设备端代码,并准备它连接到 HTTP Handler。我将做的第一件事是停止使用 MockHTTPTransport
实例,并开始使用 KbHTTPTransport
。虽然 MockHTTPTransport
只是模拟 HTTP 请求以允许在设备上测试所有渲染逻辑,但 KbHTTPTransport
才是真正的实现。这是我在 Articles Screen 或 Tags Screen 中使用它的方式:
private void sendHttpRequest(String[] keys, String[] values) {
articlesList.set(null); // Show "No Articles" while we download.
String url = DataStore.getAppServerUrl();
// Make sure we can create an http request.
// If the app. server URL has not been set, we cannot make any http requests.
if (null == url || url.length() == 0) {
Dialog.alert(resources.getString(
KnowledgeBaseResource.ERR_INVALID_APP_SERVER_URL));
return;
}
statusDlg.show();
byte[] requestContents = TransportUtils.createPostData(keys, values);
KbHTTPTransport transport = new KbHTTPTransport();
transport.setHTTPTransportListener(this);
transport.send(KbHTTPTransport.REQ_METHOD_POST, url, requestContents);
}
至此,所有组件都已就位,我将使用 BlackBerry 模拟器和 MDS 模拟器来测试带有真实数据的应用程序。启动两个模拟器后,我将检查 Options Screen,确保我指向了正确的 HTTP Handler URL。
回到 Main Screen,我选择“浏览标签”。
在这里,我从 Handler 中获取到了真实数据。
选择一个标签应该会返回一些文章。
选择一篇文章应该会显示其内容。
结论
我希望你觉得这一系列的帖子很有帮助。我认为这个应用程序已经达到了一个合理的质量水平,你可以将其模块作为自己项目的 foundational。虽然我将转向其他主题,但我愿意回到工作中或就你感兴趣的任何领域进行更多讨论。请告诉我你的想法。
历史
以下是本系列前几篇文章的链接: